Skip to content

[EE] CSGLite edge serving connector - #247

Merged
ganisback merged 9 commits into
mainfrom
feat/ee-edge-serving
Nov 13, 2026
Merged

ganisback merged 9 commits into
mainfrom
feat/ee-edge-serving

Conversation

@ganisback

Copy link
Copy Markdown
Collaborator

Summary

  • Add ee/edge: CSGLite edge serving connector. Lite dials the AI gateway over an outbound WebSocket (/v1/edge/connect), multiplexes streams with yamux, and serves approved local models through the tunnel.
  • Keep the tunnel healthy: 5s heartbeat reporting models, load and active requests; 15s gateway watchdog; yamux keepalive with write deadlines; exponential backoff reconnect (1s–60s, jittered) that resets after 30s stable and reconnects immediately on gateway or sign-in changes.
  • Add /api/edge for node status, model list and application review, gated by the new feature.lite.edge_serving enterprise license feature.
  • Add the Edge Serving page, API client and i18n strings; surface edge models on the AI Gateway page.
  • Sync openapi/local-api.json and document the newly gated feature in docs/agent-guidelines/ee-features.md.

Test plan

  • go test ./ee/edge ./internal/server ./internal/license
  • cd web && npx vitest run src/edge.test.ts
  • Manual against a local aigateway: tunnel connects, approved models appear in the market, streaming requests are relayed end to end; killing or freezing lite takes the node offline within ~15s and it reconnects automatically.

Comment thread ee/edge/manager.go
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
m.done = make(chan struct{})
m.draining = make(chan struct{})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] m.draining 无锁读写:并发开关会触发 data race,旧 loop 还可能读到新 channel 导致 drain 失效

现象与触发场景

run() 在 :284 持锁写入 m.draining,但 loop()(:330、:337、:373)和 session()(:466)都在 select 里不加锁直接读这个字段。用户在 Edge 页面连续切换开关时(PUT /api/edge → SetEnabled → Stop() / run()),旧 loop 或正在 drain 的 session 读到的是 run() 刚替换的新 channel,收不到关闭信号:Stop() 只能等 15s 超时后 cancel() 才返回;如果此时新 loop 已经起来,两个 loop 会各自建隧道,网关侧 previous.close("replaced") 把彼此顶掉,表现为连接反复闪断。go test -race 会直接报这一对读写。现有 manager_test.go 都是串行调用 SetEnabled,覆盖不到并发路径。

破坏的承诺

PR 描述承诺 "exponential backoff reconnect ... that resets after 30s stable",开关是用户可随时操作的控制面;draining 是这条控制面唯一的停止信号,读到旧/新 channel 的竞态会让"关闭"变成"15 秒后被迫取消"甚至"两个连接同时存在"。Risks 里没有覆盖并发开关场景,Non-goals 也没有排除。

修复建议

照 done 的做法:在 run() 里把 draining 取成局部变量传给 loop/session(done := m.done 已经是这个写法),并在 Stop() 持锁时把 m.draining 置 nil,禁止后续读取字段本身。

需要补的测试

并发 SetEnabled(true) / SetEnabled(false) 各若干次,在 -race 下运行;断言 Stop() 返回后不再有新的 Models() / 重连调用(例如统计 fakeHost 的 Models 调用次数在 stop 后不再增长)。

@ganisback
ganisback merged commit a021eaa into main Nov 13, 2026
4 checks passed
@ganisback
ganisback deleted the feat/ee-edge-serving branch November 13, 2026 01:29
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