Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Concurrency Tests between Array Operations #985

Merged
merged 12 commits into from
Sep 2, 2024

Conversation

cloneot
Copy link
Contributor

@cloneot cloneot commented Aug 28, 2024

What this PR does / why we need it:
Implement Array.SetByIndex operation partially.
Add conccurency tests between array operations(Insert, Move, Set, Remove).

Problem Space Definition / Test Implementation

Problem Space Definition

  1. Each operation refers to certain Elements:
    • Insert: Previous Element, Previous.Next Element
    • Move: Previous Element, Previous.Next Element, Target Element
    • Set: Target Element
    • Remove: Target Element
  2. Problems can occur if concurrent operations op1 and op2 modify/refer to the same Element:
    • e.g., concurrent insertions at the same position: Insert(op1).Prev == Insert(op2).Prev
    • e.g., concurrent moves of the same element: Move(op1).Target == Move(op2).Target
    • e.g., concurrent deleting/updating an element: Remove(op1).Target == Set(op2).Target
  3. Test all possible scenarios where op1.x == op2.y (7 * 7)
    • Note: Not symmetric
    • Order of lamport timestamp between op1 and op2 affects the outcome

Test Implementation

  • Consider op1.x == op2.y == index k and fix k (== oneIdx)
  • List the 7 cases above in the operations array
  • Iterate through all (op1, op2) pairs and test
type arrayOp struct {
	opName   string
	executor func(*json.Array, int)
}

// NOTE(junseo): It tests all (op1, op2) pairs in operations.
// `oneIdx` is the index where both op1 and op2 reference.
// `opName` represents the parameter of operation selected as `oneIdx'.
// `otherIdxs` ensures that indexs other than `oneIdx` are not duplicated.
operations := []arrayOp{
	// insert
	{"insert.prev", func(a *json.Array, cid int) {
		// oneIdx(1) 뒤에 새 값(5 또는 6)을 삽입
		// insert.prev == `oneIdx`
		// 예: [1,2,3,4] -> [1,2,5,3,4] 또는 [1,2,6,3,4]
		a.InsertIntegerAfter(oneIdx, newValues[cid])
	}},
	{"insert.prev.next", func(a *json.Array, cid int) {
		// oneIdx(1) 앞에 새 값(5 또는 6)을 삽입
		// insert.prev.next == `oneIdx`
		// 예: [1,2,3,4] -> [1,5,2,3,4] 또는 [1,6,2,3,4]
		a.InsertIntegerAfter(oneIdx-1, newValues[cid])
	}},

	// move
	{"move.prev", func(a *json.Array, cid int) {
		// oneIdx(1) 뒤로 otherIdxs[cid](2 또는 3)의 값을 이동
		// move.prev == `oneIdx`
		// 예: [1,2,3,4] -> [1,2,4,3] 또는 [1,2,3,4](3을 2 뒤로 이동하면 변화 없음)
		a.MoveAfterByIndex(oneIdx, otherIdxs[cid])
	}},
	
	// ...
}
for _, op1 := range operations {
	for _, op2 := range operations {
		t.Run(op1.opName+" vs "+op2.opName, func(t *testing.T) {
			result := runTest(op1, op2)
			if !result.flag {
				t.Skip(result.resultDesc)
			}
		})
	}
}

Tests (7 * 7 = 49 cases)

  • Range (7 = 2+3+1+1)
    • Insert (2: Prev, Prev.Next)
    • Move (3: Prev, Prev.Next, Target)
    • Set (1: Target)
    • Remove (1: Target)
      image

Failure cases (2*2 + 2 + 1*2 + 1*2 = 10 cases)

  1. *.Prev == Move.Target (2*2 cases, symmetric)
  • Insert.Prev == Move.Target
  • Move.Prev == Move.Target
  1. *.Prev.Next == Set.Target (2 cases, asymmetric)
  • Insert.Prev.Next == Set.Target
  • Move.Prev.Next == Set.Target
  1. Move.Target == Set.Target (1*2 cases, symmetric, client id matters)
  2. Remove.Target == Set.Target (1*2 cases, symmetric)

Why failure occurs

  1. Implement CRDT Move Convergence Mechanism #987.
  2. It's because Set operation updates movedAt. updatedAt required.
  3. It's because Set operation updates movedAt. updatedAt required.
  4. It's because Set operation does not copy removedAt of old element.

Which issue(s) this PR fixes:

Fixes yorkie-team/yorkie-js-sdk#191

Special notes for your reviewer:
I initially created the hand-written simple test cases in func TestArrayConcurrency. To ensure all cases are covered, I also added tests for all operation pairs in func TestArrayConcurrencyTable.
The former can be reproduced in the latter. Would it be better to delete the former tests? And would it be better to create a new test file for array concurrency, similar to tree_concurrency_test.go?

Additional documentation:

Future works: (#990)

  • Add crdt metadata comparison in TestArrayConcurrencyTable. (currently, compare doc.Marshal() only)
  • Support other types in SetByIndex operation. (SetDouble, SetString, ...)
  • Add GC logic in SetByIndex operation.
  • Add GC tests for SetByIndex operation.
  • Add updatedAt in Element interface.

Checklist:

  • Added relevant tests or not required
  • Didn't break anything

Summary by CodeRabbit

  • New Features

    • Introduced the SetByIndex operation for enhanced array manipulation, allowing elements to be set at specific indices.
    • Added methods MoveAfterByIndex and SetInteger to the Array type for improved element repositioning and integer value setting.
  • Bug Fixes

    • Enhanced error handling in the new methods to ensure robust operation during concurrent modifications.
  • Tests

    • Expanded testing suite for array operations, including new tests for concurrency and setting values at specific indices.

Copy link

coderabbitai bot commented Aug 28, 2024

Walkthrough

The changes introduce a new operation type, SetByIndex, to the Yorkie API, enhancing the functionality for manipulating array elements by index. This includes updates to various files, such as converters, OpenAPI specifications, and core data structures, to support the new operation. Additionally, new methods and test cases are added to ensure proper handling and validation of concurrent operations involving the SetByIndex feature.

Changes

Files Change Summary
api/converter/from_pb.go, api/converter/to_pb.go Added handling for SetByIndex operations in conversion functions, with new helper functions for processing.
api/docs/yorkie/v1/admin.openapi.yaml, api/docs/yorkie/v1/resources.openapi.yaml, api/docs/yorkie/v1/yorkie.openapi.yaml Introduced new schema definition for SetByIndex in OpenAPI specifications, detailing its properties.
api/yorkie/v1/resources.proto Added new message type SetByIndex to the Operation message, including relevant fields and updates.
pkg/document/crdt/array.go, pkg/document/crdt/rga_tree_list.go Introduced SetByIndex methods to Array and RGATreeList structs for indexed element manipulation.
pkg/document/json/array.go Added MoveAfterByIndex and SetInteger methods for enhanced array manipulation.
pkg/document/operations/set_by_index.go Defined SetByIndex operation, including struct, constructor, and execution logic for array modifications.
test/integration/array_test.go Added new test functions to validate concurrent operations on arrays, including tests for SetByIndex.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant Document
    participant Array

    Client->>API: Request SetByIndex
    API->>Document: Validate operation
    Document->>Array: Set element at index
    Array-->>Document: Acknowledge change
    Document-->>API: Return success
    API-->>Client: Respond with result
Loading

🐰 "In the meadow, hopping free,
New changes bloom like flowers, you see!
SetByIndex, oh what delight,
Manipulating arrays feels just right!
With tests to keep it all in line,
A joyful dance, oh how we shine!" 🌼

Assessment against linked issues

Objective Addressed Explanation
Provide value setting API based on Array index (#191)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.
Early access features: disabled

We are currently testing the following features in early access:

  • Anthropic claude-3-5-sonnet for code reviews: Anthropic claims that the new Claude model has stronger code understanding and code generation capabilities than their previous models. Note: Our default code review model was also updated late last week. Please compare the quality of the reviews between the two models by toggling the early access feature.

Note:

  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.
  • Please join our Discord Community to provide feedback and report issues on the discussion post.

@CLAassistant
Copy link

CLAassistant commented Aug 28, 2024

CLA assistant check
All committers have signed the CLA.

@cloneot cloneot marked this pull request as ready for review August 29, 2024 09:23
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Outside diff range, codebase verification and nitpick comments (4)
pkg/document/crdt/rga_tree_list.go (1)

349-354: Add documentation for the SetByIndex method.

Consider adding a docstring to explain the purpose and usage of the SetByIndex method.

pkg/document/json/array.go (2)

170-173: Add documentation for the MoveAfterByIndex method.

Consider adding a docstring to explain the purpose and usage of the MoveAfterByIndex method.


311-321: Add documentation for the SetInteger method.

Consider adding a docstring to explain the purpose and usage of the SetInteger method.

api/converter/to_pb.go (1)

380-394: Add documentation for the toSetByIndex function.

Consider adding a docstring to explain the purpose and usage of the toSetByIndex function.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 3f4f5d3 and a878df0.

Files ignored due to path filters (1)
  • api/yorkie/v1/resources.pb.go is excluded by !**/*.pb.go
Files selected for processing (11)
  • api/converter/from_pb.go (2 hunks)
  • api/converter/to_pb.go (2 hunks)
  • api/docs/yorkie/v1/admin.openapi.yaml (2 hunks)
  • api/docs/yorkie/v1/resources.openapi.yaml (2 hunks)
  • api/docs/yorkie/v1/yorkie.openapi.yaml (2 hunks)
  • api/yorkie/v1/resources.proto (2 hunks)
  • pkg/document/crdt/array.go (1 hunks)
  • pkg/document/crdt/rga_tree_list.go (1 hunks)
  • pkg/document/json/array.go (3 hunks)
  • pkg/document/operations/set_by_index.go (1 hunks)
  • test/integration/array_test.go (1 hunks)
Additional comments not posted (28)
pkg/document/operations/set_by_index.go (10)

1-15: LGTM!

The license header is correctly formatted.


17-22: LGTM!

The package declaration and imports are correctly formatted.


24-37: LGTM!

The SetByIndex struct definition is correctly formatted and includes necessary fields.


39-52: LGTM!

The NewSetByIndex constructor function is correctly implemented.


54-80: LGTM! But address the commented-out code.

The Execute method is correctly implemented. However, the commented-out code for GC-like logic should be addressed or removed if not needed.


82-85: LGTM!

The Value method is correctly implemented.


87-90: LGTM!

The ParentCreatedAt method is correctly implemented.


92-95: LGTM!

The ExecutedAt method is correctly implemented.


97-100: LGTM!

The SetActor method is correctly implemented.


102-105: LGTM!

The CreatedAt method is correctly implemented.

pkg/document/crdt/array.go (3)

Line range hint 1-15: LGTM!

The license header is correctly formatted.


Line range hint 17-22: LGTM!

The package declaration and imports are correctly formatted.


207-217: LGTM!

The SetByIndex method is correctly implemented.

api/yorkie/v1/resources.proto (3)

Line range hint 1-15: LGTM!

The license header is correctly formatted.


139-145: LGTM!

The SetByIndex message definition is correctly formatted and includes necessary fields.


158-158: LGTM!

The update to the oneof body section is correctly implemented.

pkg/document/crdt/rga_tree_list.go (1)

360-369: Clarify the TODO comment and update timestamp usage.

The TODO comment suggests using UpdatedAt instead of MovedAt. Ensure that the correct timestamp is used for the intended logic.

Verify if UpdatedAt should be used instead of MovedAt and update the code accordingly.

api/converter/to_pb.go (1)

386-393: Improve error message clarity.

The error message "toSetByIndex %s: %w" could be more descriptive to indicate the specific error.

Apply this diff to improve the error message:

- return nil, fmt.Errorf("toSetByIndex %s: %w", setByIndex, err)
+ return nil, fmt.Errorf("toSetByIndex: failed to convert value for setByIndex operation: %w", err)

Likely invalid or redundant comment.

api/converter/from_pb.go (1)

544-567: LGTM!

The function fromSetByIndex is correctly implemented. It handles the conversion of all necessary fields and manages errors appropriately.

test/integration/array_test.go (3)

475-568: LGTM!

The function TestArrayConcurrencyTable is well-structured and comprehensive. It systematically tests various operations on an array in a concurrent context, ensuring consistency in the results.


570-608: LGTM!

The function TestArraySetByIndex is correctly implemented. It includes detailed test cases and assertions to validate the expected state of the arrays after operations.


610-762: LGTM!

The function TestArrayConcurrency is correctly implemented. It includes detailed test cases and assertions to validate the expected state of the arrays after operations.

api/docs/yorkie/v1/yorkie.openapi.yaml (2)

703-708: LGTM!

The addition of setByIndex under yorkie.v1.Operation is correctly formatted and follows the existing pattern for other operations.


978-1007: LGTM!

The definition of yorkie.v1.Operation.SetByIndex is correctly formatted and follows the existing pattern for other operations.

api/docs/yorkie/v1/resources.openapi.yaml (2)

719-724: LGTM!

The addition of setByIndex in the yorkie.v1.Operation schema is consistent with the existing structure of other operations.


994-1023: LGTM!

The definition of the SetByIndex schema is correct and follows the existing pattern for other operation schemas.

api/docs/yorkie/v1/admin.openapi.yaml (2)

1210-1215: LGTM!

The addition of the setByIndex property to the yorkie.v1.Operation schema is consistent with the introduction of the new SetByIndex operation.


1485-1514: LGTM!

The definition of the yorkie.v1.Operation.SetByIndex schema is well-structured and consistent with the existing schemas. The description provides useful context for the value.created_at property.

pkg/document/crdt/rga_tree_list.go Show resolved Hide resolved
pkg/document/crdt/rga_tree_list.go Show resolved Hide resolved
pkg/document/json/array.go Outdated Show resolved Hide resolved
pkg/document/json/array.go Outdated Show resolved Hide resolved
api/converter/to_pb.go Show resolved Hide resolved
@cloneot cloneot changed the title Operation set by index Add Concurrency Tests between Array Operations Aug 29, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Outside diff range, codebase verification and nitpick comments (1)
pkg/document/json/array.go (1)

416-447: Review the implementation of setByIndexInternal.

This internal helper function supports the SetInteger method by handling the creation of a new element and its insertion into the array. The function correctly issues a time ticket, creates a new primitive element, and registers the set operation in the context. However, there is a commented-out section related to garbage collection logic, which suggests potential future enhancements or considerations.

Consider finalizing or removing the commented-out code related to garbage collection to clean up the implementation and avoid confusion.

- // if removed != nil {
- // 	// TODO(junseo): add GC-like logic
- // 	// p.context.RegisterRemovedElementPair(p, removed)
- // }
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between a878df0 and 9de45cf.

Files selected for processing (1)
  • pkg/document/json/array.go (9 hunks)
Additional comments not posted (1)
pkg/document/json/array.go (1)

401-414: Review the implementation of moveAfterInternal.

This internal helper function is used to move elements within the array based on their creation timestamps. The implementation correctly issues a time ticket and registers the move operation in the context. Error handling is present, ensuring that any issues during the move operation are caught and handled appropriately.

The code changes are approved.

pkg/document/json/array.go Outdated Show resolved Hide resolved
pkg/document/json/array.go Outdated Show resolved Hide resolved
@cloneot
Copy link
Contributor Author

cloneot commented Aug 30, 2024

What is intended behavior for invalid index?
I added negative index validation for json array methods in 9de45cf. But it still seems like there could be a potential 'nil pointer dereference' error.

In json array, GetObject, GetArray, ... , Delete method validate index itself and call *crdt.Array.Get directly. This is ok.
But, InsertIntegerAfter(and MoveAfterByIndex, SetInteger) method call *json.Array.Get which validates index and returns nil or *crdt.Array.Get(idx).
It can be executed nil.CreatedAt() if p.Get(index) returns 'nil' in following statements.

func (p *Array) InsertIntegerAfter(index int, v int) *Array {
	p.insertAfterInternal(p.Get(index).CreatedAt(), func(ticket *time.Ticket) crdt.Element {
		primitive, err := crdt.NewPrimitive(v, ticket)
		if err != nil {
			panic(err)
		}
		return primitive
	})

	return p
}

Update) Refactor index validation in json array in cef5b8f

  • Delegate index validation to Get and Delete methods.
  • GetObject, GetArray, GetText, GetCounter, GetTree return 'nil' for invalid index.
  • InsertIntegerAfter, MoveAfterByIndex, SetInteger panic with "index out of bound" for invalid index.

This might be related to PR #524, so I added panic method only in scenarios where a 'nil pointer dereference' occurs.

Delegate index validation to Get and Delete methods.
GetObject, GetArray, GetText, GetCounter, GetTree return 'nil' for invalid index.
InsertIntegerAfter, MoveAfterByIndex, SetInteger panic with "index out of bound" for invalid index.
@cloneot cloneot self-assigned this Aug 30, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 9de45cf and cef5b8f.

Files selected for processing (1)
  • pkg/document/json/array.go (9 hunks)

pkg/document/json/array.go Show resolved Hide resolved
pkg/document/json/array.go Show resolved Hide resolved
Copy link
Contributor

@chacha912 chacha912 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution🚀. I've left a few comments, please check them.

pkg/document/crdt/rga_tree_list.go Outdated Show resolved Hide resolved
pkg/document/json/array.go Outdated Show resolved Hide resolved
pkg/document/operations/set_by_index.go Outdated Show resolved Hide resolved
test/integration/array_test.go Outdated Show resolved Hide resolved
test/integration/array_test.go Outdated Show resolved Hide resolved
- Add note for array concurrency table tests.
- Expand the testing coverage from the upper diagonal to the entire matrix.
- Remove duplicated tests.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between cef5b8f and 9c5401e.

Files selected for processing (4)
  • pkg/document/crdt/rga_tree_list.go (1 hunks)
  • pkg/document/json/array.go (9 hunks)
  • pkg/document/operations/set_by_index.go (1 hunks)
  • test/integration/array_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (3)
  • pkg/document/crdt/rga_tree_list.go
  • pkg/document/json/array.go
  • pkg/document/operations/set_by_index.go
Additional comments not posted (2)
test/integration/array_test.go (2)

475-569: Comprehensive concurrency testing for array operations. LGTM!

The TestArrayConcurrencyTable function provides thorough testing of concurrent array operations:

  • Covers key operations: insert, move, set, remove
  • Tests all pairs of operations
  • Verifies consistency of array state across clients
  • Well-structured with the use of arrayOp struct and runTest helper

The test code looks good to me. Nice work!


571-608: Good test for setting array elements by index concurrently. Approved!

The TestArraySetByIndex function tests an important scenario of setting array elements by index concurrently from multiple clients.

The test initializes the array and then concurrently sets values at two different indices. The assertions verify that the final array state is as expected and consistent across the clients.

Overall, this is a well-written test case. No issues found.

Copy link
Contributor

@chacha912 chacha912 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks good to me :)

Copy link
Member

@hackerwins hackerwins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution.

@hackerwins
Copy link
Member

hackerwins commented Sep 2, 2024

I initially created the hand-written simple test cases in func TestArrayConcurrency. To ensure all cases are covered, I also added tests for all operation pairs in func TestArrayConcurrencyTable.
The former can be reproduced in the latter. Would it be better to delete the former tests?

If both tests are testing the same case, it may be good to remove first one.

And would it be better to create a new test file for array concurrency, similar to tree_concurrency_test.go?

The test code isn't very big, so I think it's okay for it to be in the same file for now.

@cloneot cloneot mentioned this pull request Sep 2, 2024
11 tasks
@hackerwins hackerwins merged commit 256bf02 into yorkie-team:main Sep 2, 2024
5 checks passed
hackerwins added a commit that referenced this pull request Sep 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide value setting API based on Array index
4 participants