Skip to content

bugfix: Race condition on agent.Active field.#314

Open
AeonDave wants to merge 2 commits into
Adaptix-Framework:dev-v1.3from
AeonDave:dev-v1.3
Open

bugfix: Race condition on agent.Active field.#314
AeonDave wants to merge 2 commits into
Adaptix-Framework:dev-v1.3from
AeonDave:dev-v1.3

Conversation

@AeonDave
Copy link
Copy Markdown

@AeonDave AeonDave commented Apr 8, 2026

agent.Active is a plain bool read and written from multiple goroutines without any mutex or atomic protection.

A goroutine can read a stale value of Active while another goroutine is setting it to false. This leads to tasks being dispatched to terminated agents, or active agents being incorrectly skipped. Under the Go memory model, unsynchronized bool access is a data race.

Protect Active with sync.Mutex or use atomic.Bool. All reads and writes must go through the same synchronization.

`agent.Active` is a plain `bool` read and written from multiple goroutines without any mutex or atomic protection.

A goroutine can read a stale value of `Active` while another goroutine is setting it to `false`. This leads to tasks being dispatched to terminated agents, or active agents being incorrectly skipped. Under the Go memory model, unsynchronized bool access is a data race.
Copilot AI review requested due to automatic review settings April 8, 2026 20:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency bug in the Teamserver’s Agent lifecycle handling by eliminating unsynchronized access to the agent “active” state, preventing tasks/tunnels/terminal actions from being dispatched to terminated agents.

Changes:

  • Replaced Agent.Active bool with an atomic.Bool and added IsActive() / SetActive() accessors.
  • Updated call sites to use IsActive() checks and SetActive(false) on termination/restore.
  • Adjusted axc2 imports in touched files to use an explicit adaptix import name.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
AdaptixServer/core/server/utils.go Changes Agent active state to atomic.Bool and adds accessors.
AdaptixServer/core/server/ts_tunnels.go Uses agent.IsActive() to prevent starting tunnels for inactive agents.
AdaptixServer/core/server/ts_terminal.go Uses agent.IsActive() to prevent terminal channel creation for inactive agents.
AdaptixServer/core/server/ts_agent.go Initializes active state via SetActive(true) and terminates via SetActive(false); replaces active checks.
AdaptixServer/core/server/server.go Restores agent active state via SetActive(true/false) based on persisted mark.
AdaptixServer/core/server/mgr_task.go Prevents task creation for inactive agents via IsActive().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 68 to +72
mu sync.RWMutex
data adaptix.AgentData
Extender adaptix.ExtenderAgent
Tick bool
Active bool
active atomic.Bool
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Agent.Tick remains a plain bool but is read/written from multiple goroutines (e.g., set in TsAgentProcessData and polled/reset in the background TsAgentTickUpdate loop started from server.go). This is still a data race under the Go memory model. Consider protecting Tick with the same synchronization approach as active (e.g., atomic.Bool with IsTicked/SetTicked, or guard it with Agent.mu).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

added on next commit

`agent.Tick` is a plain `bool` read and written from multiple goroutines
without any mutex or atomic protection.

`TsAgentProcessData` writes `Tick = true` from listener/handler goroutines
while `TsAgentTickUpdate` concurrently reads and resets it in a background
loop (800ms interval).
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.

2 participants