Repository navigation
fix(dedup): answer every command on the control socket, say proto=2 - #190
Merged
Merged
Conversation
The unix control socket answered ACK even when a START's counter reset failed or an END's coverage was never sent, and closed with no answer on a blank line or a command it could not parse. Keploy read each of those as taken. A command that threw (a JaCoCo port out of range) ended the accept loop, so every later test's command was refused. Every command the SDK reads now gets one line before the close: "ACK proto=2" when it was taken, "IGNORED proto=2" when not. A START whose reset failed is ignored and so is its END; an END whose coverage could not be collected or sent is ignored and still ends the test. A command that throws is ignored and the server goes on. Only a connection closed with nothing sent gets no answer. proto=2 says this SDK answers every command it reads, so Keploy can read a close with no answer as a command not taken: on macOS a connection closed with its command unread gives the reader a plain close, not an error. Agents that read only an IGNORED prefix, or no answer at all, read "ACK proto=2" as ACK. The TCP transport is unchanged. The data socket path is a field, as the control socket's is, so the rows can bind their own. The smoke test now requires the exact answer. Signed-off-by: slayerjain <shubhamkjain@outlook.com>
stop() closed the unix control socket from the caller's thread and returned at once, with the accept thread still running. junixsocket's accept holds the socket's raw fd number, and when it finds the socket file gone it closes that number. With start() right after stop(), that number could already be the new server's socket, which was then closed with its file still there: every later command was refused. The old thread also deleted the socket file by path once it ended, so a start() on the same path, as the default /tmp/coverage_control.sock always is, lost its file, and every later command found no socket. Once junixsocket had closed the socket under it, the accept loop went on: it spun a core, logged without end, and isStarted() still said true. Now only the accept thread closes its socket. stop() wakes that accept with a connection of its own and waits up to 5 s for the thread to end, so a command in flight is answered first; a start() after it waits for that thread too. A socket closed under the server (its file deleted, or bound again by another process) is said once and ends the server. On the TCP transport, a START whose counter reset failed was answered ACK, and its END sent the lines of the tests before it as this test's. It is now answered "IGNORED <id>" and then ACK, as an END not taken is, and its END is ignored too. A START whose reset threw (a JaCoCo port out of range) ended the TCP thread, so no later command was answered; it is now ignored and the connection goes on. Signed-off-by: slayerjain <shubhamkjain@outlook.com>
junixsocket's delete on close deletes the socket file by path. When another server had bound the control socket's path since (the file deleted and bound again), stop() deleted that server's file. The SDK now turns it off and deletes the file once its socket is closed, only while the file is still the one it bound (the same device and inode). On the TCP transport, an END whose JaCoCo dump failed sent an empty COV, the frame of a test that ran no line of the app, so the collector could not tell the two apart. It is now answered "IGNORED <id>" and then ACK, as on the unix socket, and it ends the test. stop() waits for the worker's thread through one interface for both transports. When its wait is over with the thread still running, it closes the socket, says so at WARNING and returns, where it waited a second time before: a start() waits for that thread in turn. bind() closes the socket it opened when it fails, so close() is only ever called on a listening socket: it wakes the accept, and closes the socket from the caller's thread only when the wake-up cannot connect. The tests shorten the waits. New rows: stop() during a command leaves the socket open to its own thread; stop() after another server took the path ends the thread and leaves that server's file; stop() after the socket file was deleted ends at once; start() while the stopped worker still runs a command refuses, and binds once that worker has ended; a TCP END whose dump failed. The deleted-file row now also checks that it is said once. Signed-off-by: slayerjain <shubhamkjain@outlook.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
On macOS, Keploy's agent cannot tell a coverage command the SDK read and took from one it never read. A unix connection closed with its command unread gives the reader a plain close there, not a reset, so the agent counts that command as taken and says nothing. To tell the two apart, every SDK must answer every command it reads, and say that it does.
Describe the changes you've made
Every command gets an answer. Before, the unix control socket answered ACK even when a START's counter reset failed, and even when an END's coverage was never collected or sent. It closed with no answer on a blank line or on a line it could not parse. Keploy read each of those as taken. Now every command the SDK reads gets one line before the close:
ACK proto=2IGNORED proto=2, and its END is ignored tooACK proto=2IGNORED proto=2(the test still ends)IGNORED proto=2, nothing sentIGNORED proto=2IGNORED proto=2Only a connection closed with nothing sent gets no answer, as there is no command to answer.
A command that throws no longer stops the server. A RuntimeException in a command (for example a JaCoCo port out of range, which makes the reset throw IllegalArgumentException) left the accept loop. The SDK then refused every later test's command, and dedup was off for the rest of the run. That command is now answered
IGNORED proto=2and the server goes on.proto=2tells Keploy that this SDK answers every command it reads. Keploy can then read a close with no answer as a command not taken.Compatibility
IGNORED, and older ones read every answer as taken. Either wayACK proto=2reads asACKdid, andIGNORED proto=2asIGNORED.IGNOREDnow keep those tests out of dedup instead of counting them as covered.IGNORED <id>and thenACK, the frame an END not taken already sends, and its END is ignored too. Before, it was answeredACKand its END sent the lines of the tests before it as this test's. Collectors that do not know the frame skip it and still read theACK.COV, the frame of a test that ran no line of the app, so the collector could not tell the two apart. It is now answeredIGNORED <id>and thenACK, as on the unix socket, and it ends the test.stop() and start()
isStarted()still said true.isStarted()says false.Tested
ControlSocketTest, 19 rows. A fake JaCoCo tcpserver (it can drop a connection unanswered, as a JaCoCo that went away does) and a fake data socket run each answer in the table:ACK proto=2, and its coverage reaches the data socket;ACK proto=2to its START and END, against the real JaCoCo agent.isStarted()false;CoverageTcpClientTesthas three more: a START whose reset failed, with the tests around it; a START that throws; an END whose dump failed, which ends its test.ControlSocketTeston JDK 21, and 3000 start-and-stop rounds on fresh paths on JDK 17 and 21. By junixsocket's accept code, it is the same cause: accept closes the fd number it read when it finds the socket file gone, so a stop() that closed the socket from another thread could close the next server's socket.ControlSocketTestandCoverageTcpClientTest3 more times, all green.proto=2dropped from either answer, a line that is no command taken, a failed reset unread or kept, a failed START keeping the test before it, the END match, a mismatched or unsent END taken, the test not ended; stop() closing at once, not waiting, or leaving the socket open after its wait; close() closing from outside, or leaving an accept no wake-up reaches; start() binding while the old worker runs; an accept closed by stop() taken for a lost socket file; a socket closed under the server looping on or still started; the socket file deleted whoever's it is, never deleted, or by junixsocket's delete on close; and on TCP, the test set before its reset or kept after a failed one, a failed START or dump naming no test, a throwing START ending the connection, the reset unread, a failed dump sending an empty COV or not ending its test.Type of change
Checklist