Skip to content

Commit

Permalink
fix: avoid deadlock with multiple connections when executing code (xd…
Browse files Browse the repository at this point in the history
…ebug#294)

This introduces a new concept in the xDebug connection:  "execution commands": these are commands that result in PHP being executed.  The way xDebug works, if one of these commands is run, that connection is hung until the PHP code is done executing (until it gets to the next stopping point).  By keeping track of when such commands are still in progress, and making it something that the adapter can check, it allows the adapter to specifically code for when the connection is currently hung.  Which allows preventing the deadlock situation described in xdebug#164 

The adapter also now emits `before-execution-command` since it results in possibly going into a hung state for a while.  The adapter uses that event to send a `ContinueEvent`, this is just a side advantage that makes the interface more responsive.  Before when you click continue, if the PHP code is hung for whatever reason (it is slow, it has a lot to do, etc), it appears that it did not work as it appears to still be stopped on a breakpoint (but it is not, which you can tell by trying to view any of the details in the debug pane).  With the change, now when you click continue it immediately changes the process back to "running" state which is more truthful.

Since xdebug#164 has a lot of comments, here is the **tl;dr:** deadlock explanation: 
1. Connection 1 makes a second connection with curl.
2. It sends command to all connections to set breakpoints, and waits for response.
3. The second connection hangs as it is waiting for connection 1 to set breakpoints, connection 1 hangs without setting those breakpoints because it is waiting for connection 2 to respond and can't do anything until that completes.

This PR makes it not wait for the breakpoints to be set on a connection if the connection is waiting for code to execute.  So now connection 2 can finish initializing and run, no more deadlock.

Fixes xdebug#164
  • Loading branch information
jonyo authored and SidIcarus committed Oct 9, 2018
1 parent c416413 commit 2636450
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ class PhpDebugSession extends vscode.DebugSession {
})
connection.on('error', disposeConnection)
connection.on('close', disposeConnection)
connection.on('before-execute-command', () => {
// It is about to start executing PHP code
this.sendEvent(new vscode.ContinuedEvent(connection.id))
})
await connection.waitForInitPacket()

// override features from launch.json
Expand Down
14 changes: 14 additions & 0 deletions src/xdebugConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,15 @@ export class Connection extends DbgpConnection {
})
}

/** Adds the given command to the queue, or executes immediately if no commands are currently being processed. */
private _enqueue(command: Command): void {
if (this._commandQueue.length === 0 && this._pendingCommands.size === 0) {
this._executeCommand(command)
} else {
this._commandQueue.push(command)
}
}

/**
* Pushes a new execute command (one that results in executing PHP code) to the queue that will be executed after all the previous
* commands have finished and we received a response.
Expand Down Expand Up @@ -679,6 +688,11 @@ export class Connection extends DbgpConnection {
const data = iconv.encode(commandString, ENCODING)
this._pendingCommands.set(transactionId, command)
this._pendingExecuteCommand = command.isExecuteCommand
if (this._pendingExecuteCommand) {
// Since PHP execution commands block anything on the connection until it is
// done executing, emit that the connection is about to go into such a locked state
this.emit('before-execute-command')
}
await this.write(data)
}

Expand Down

0 comments on commit 2636450

Please sign in to comment.