Skip to content

[live-migration] Fix source resume hang when cancelled before blackout - #2871

Open
Harsh Rawat (rawahars) wants to merge 1 commit into
microsoft:mainfrom
rawahars:reuse-same-connection
Open

[live-migration] Fix source resume hang when cancelled before blackout#2871
Harsh Rawat (rawahars) wants to merge 1 commit into
microsoft:mainfrom
rawahars:reuse-same-connection

Conversation

@rawahars

Copy link
Copy Markdown
Contributor

On a source rollback, Resume unconditionally re-armed the log listener and re-accepted the guest GCS bridge, assuming a blackout had dropped them. When the migration is cancelled before blackout (e.g. cancel after PrepareAndExport), those connections are still live, so both accepts block until the GCS connection timeout. finalize(resume) then fails with DeadlineExceeded, leaving the session Cancelled and blocking cleanup.

We now track whether the bridge transport is still live and, on resume, reuse the existing bridge and log stream instead of re-accepting when it never dropped.

@rawahars
Harsh Rawat (rawahars) requested a review from a team as a code owner August 11, 2026 09:21
On a source rollback, Resume unconditionally re-armed the log listener and
re-accepted the guest GCS bridge, assuming a blackout had dropped them. When
the migration is cancelled before blackout (e.g. cancel after
PrepareAndExport), those connections are still live, so both accepts block
until the GCS connection timeout. finalize(resume) then fails with
DeadlineExceeded, leaving the session Cancelled and blocking cleanup.

We now track whether the bridge transport is still live and, on resume, reuse the
existing bridge and log stream instead of re-accepting when it never dropped.

Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

return fmt.Errorf("resume source guest connection: %w", err)
// A source rollback before blackout never dropped the guest connection, so the
// live bridge and its log stream are reused instead of re-accepted.
reuseLiveConn := rebuildBridge && c.guest.IsBridgeConnected()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a potential race: connected is read here as a snapshot, but it is not synchronized with the recvLoopRoutine transition. If recvLoop() returns immediately before connected.Store(false) executes, Resume() can observe connected == true, set reuseLiveConn, and skip rebuilding the bridge. It can then clear migrating, after which recvLoopRoutine stores connected = false, observes migrating == false, and calls kill(err). This can leave the resumed session using a bridge that is subsequently terminated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In short, this workflow would be similar to how a pod/container is handled if the GCS connection was dropped randomly at any point today. Hence it should be safe, without introducing any backward incompatibility.

Long version:
So you are right that IsBridgeConnected is a point in time snapshot of the bridge state. Right now we are using it for Resume API which is a high level API from Controller. We can run into this API from 2 points-

  • Destination Resume: rebuildBridge is false and hence we would go into the loop. There is no prior bridge connection and therefore, we do that inside if.
  • Source Rollback:
    • After blackout: VM was paused and hence listeners dropped and bridge would have collapsed during Transfer API causing connected to be false. Therefore, it would always return false here and lead us to go inside the if.
    • Before blackout:
      • Bridge is active at the time of Resume: connected is true and we skip the if.

      • As soon as the c.guest.IsBridgeConnected() is read inside Resume, the VM terminates or connection drops for some reason.
        This is the race scenario but it's safe and follows the existing workflow. If this happens then we would mark the bridge as not migrating and finish the finalize. Since the GCS connection is not present, the bridge collapses on migrating == false and all the processes/containers signal their EXIT. containerd gets the notification and trigger delete. The caller sees the container as EXITED and pod as NOTREADY. Caller can then shut the shim.

        This would be similar to the scenario where no migration is going on and the GCS connection dropped mid-way which causes the bridge to collapse and all processes to trigger their EXIT.

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.

3 participants