-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix: resolve message queue race condition during LLM processing (#8536) #8538
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1776,4 +1776,130 @@ describe("Cline", () => { | |
| consoleErrorSpy.mockRestore() | ||
| }) | ||
| }) | ||
|
|
||
| describe("Message Queue Race Condition Fix", () => { | ||
| it("should process messages from queue when available", async () => { | ||
| const task = new Task({ | ||
| provider: mockProvider, | ||
| apiConfiguration: mockApiConfig, | ||
| task: "test task", | ||
| startTask: false, | ||
| }) | ||
|
|
||
| // Add a message to the queue | ||
| task.messageQueueService.addMessage("queued message", ["image.png"]) | ||
|
|
||
| // Call ask which should process the queued message | ||
| const result = await task.ask("followup", "Initial question") | ||
|
|
||
| // Verify the queued message was processed | ||
| expect(result.response).toBe("messageResponse") | ||
| expect(result.text).toBe("queued message") | ||
| expect(result.images).toEqual(["image.png"]) | ||
|
|
||
| // Verify queue is now empty | ||
| expect(task.messageQueueService.isEmpty()).toBe(true) | ||
| }) | ||
|
|
||
| it("should handle tool approval messages from queue", async () => { | ||
| const task = new Task({ | ||
| provider: mockProvider, | ||
| apiConfiguration: mockApiConfig, | ||
| task: "test task", | ||
| startTask: false, | ||
| }) | ||
|
|
||
| // Add a message to the queue | ||
| task.messageQueueService.addMessage("approve with context", ["image.png"]) | ||
|
|
||
| // Call ask for tool approval - should auto-approve with queued message | ||
| const result = await task.ask("tool", "Do you want to use this tool?") | ||
|
|
||
| // Verify the queued message was processed as tool approval | ||
| expect(result.response).toBe("yesButtonClicked") | ||
| expect(result.text).toBe("approve with context") | ||
| expect(result.images).toEqual(["image.png"]) | ||
|
|
||
| // Verify queue is now empty | ||
| expect(task.messageQueueService.isEmpty()).toBe(true) | ||
| }) | ||
|
|
||
| it("should check for new messages during wait period", async () => { | ||
| const task = new Task({ | ||
| provider: mockProvider, | ||
| apiConfiguration: mockApiConfig, | ||
| task: "test task", | ||
| startTask: false, | ||
| }) | ||
|
|
||
| // Mock pWaitFor to simulate adding a message during the wait | ||
| const originalPWaitFor = (await import("p-wait-for")).default | ||
| let conditionCheckCount = 0 | ||
| vi.mocked(originalPWaitFor).mockImplementation(async (condition, options) => { | ||
| // Simulate checking the condition multiple times | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3 — Test isolation: mockImplementation overrides the globally mocked p-wait-for for subsequent tests. Prefer mockImplementationOnce for this specific case or restore the mock in afterEach to avoid bleed-over. |
||
| while (true) { | ||
| conditionCheckCount++ | ||
|
|
||
| // On the second check, add a message to the queue | ||
| if (conditionCheckCount === 2) { | ||
| task.messageQueueService.addMessage("delayed message") | ||
| // The condition should now detect the message and process it | ||
| task.setMessageResponse("delayed message") | ||
| } | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2 — Test doesn't exercise the intended queue-monitoring path. This line directly sets askResponse via setMessageResponse(), so pWaitFor() returns true without the ask() loop detecting and processing the newly queued message. Remove this direct call and let ask() consume the queued message, then assert queue empties and response/text come from the queue. |
||
| // Check the condition | ||
| const result = await condition() | ||
| if (result) { | ||
| return | ||
| } | ||
|
|
||
| // Prevent infinite loop | ||
| if (conditionCheckCount > 5) { | ||
| // Force completion | ||
| task.setMessageResponse("forced completion") | ||
| return | ||
| } | ||
|
|
||
| await new Promise((resolve) => setTimeout(resolve, 10)) | ||
| } | ||
| }) | ||
|
|
||
| // Call ask - initially no messages in queue | ||
| const result = await task.ask("followup", "Question") | ||
|
|
||
| // Should have processed the message that was added during wait | ||
| expect(result.response).toBe("messageResponse") | ||
| expect(result.text).toBe("delayed message") | ||
|
|
||
| // Verify condition was checked multiple times | ||
| expect(conditionCheckCount).toBeGreaterThan(1) | ||
| }) | ||
|
|
||
| it("should handle multiple messages in queue", async () => { | ||
| const task = new Task({ | ||
| provider: mockProvider, | ||
| apiConfiguration: mockApiConfig, | ||
| task: "test task", | ||
| startTask: false, | ||
| }) | ||
|
|
||
| // Add multiple messages to the queue | ||
| task.messageQueueService.addMessage("first message") | ||
| task.messageQueueService.addMessage("second message") | ||
|
|
||
| // First ask should process first message | ||
| const result1 = await task.ask("followup", "Question 1") | ||
| expect(result1.text).toBe("first message") | ||
|
|
||
| // Queue should still have one message | ||
| expect(task.messageQueueService.isEmpty()).toBe(false) | ||
|
|
||
| // Second ask should process second message | ||
| const result2 = await task.ask("followup", "Question 2") | ||
| expect(result2.text).toBe("second message") | ||
|
|
||
| // Queue should now be empty | ||
| expect(task.messageQueueService.isEmpty()).toBe(true) | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the test 'should check for new messages during wait period', the p-wait-for implementation is overridden but not restored. Consider restoring the original implementation after the test to avoid side‐effects on subsequent tests.