Skip to content

Feat/port-file-starter-protocol - #1776

Open
Nexisato wants to merge 3 commits into
feature/corefrom
feat/gateway-starter
Open

Nexisato wants to merge 3 commits into
feature/corefrom
feat/gateway-starter

Conversation

@Nexisato

@Nexisato Nexisato commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

概述

实现 swanlab-core(Go)的进程启动与生命周期框架,以及 Python SDK 侧的握手客户端:

  • gRPC 服务启动与端点回报(port-file 最小协议)
  • Owner 生命周期:parent PID 监控、Teardown RPC、信号处理
  • 同一 core 下的多会话路由(session_id)
  • 本地信任模型:安全边界由 UDS 与文件系统权限承担
  • 端点被占用时明确失败,不静默切换传输
  • Python 侧 CoreGo:端点发现、进程管理、握手、会话绑定与有界回收
  • fork 回调注册表改用弱引用持有 bound method,注册不延长实例生命周期(为 CoreGo after-fork 预留)

Go core 功能点

启动与端点回报

Python SDK 通过 subprocess.Popen 启动 swanlab-core。未指定 --listen 时按平台自选端点:POSIX 使用 runtime 目录内的 UDS,listen 失败记录 warning 后回退 127.0.0.1:0;Windows 使用回环 TCP。listen 与 gRPC server 初始化成功后原子写入 port-file(同目录临时文件 + chmod 0600 + rename),Python 轮询该文件完成握手。显式 --listen 视为调用方指定端点,失败报错;tcp:// 限定回环地址,unix:// 要求绝对路径或抽象地址。

端点被占用时先判定占用者,再决定下一步:

  • 仍有 core 在服务:以退出码 3 失败,不切换传输、不覆盖同路径 port-file,避免同一 runtime 下并存两个实例;
  • 崩溃遗留的 socket 文件:回收后重新绑定,避免一次 SIGKILL 之后永久退化为回环 TCP;
  • 普通文件等调用方产物:不删除,按原有逻辑回退 loopback TCP。

port-file 为最小协议,承担临时 endpoint discovery,不携带鉴权或身份字段:

protocol=1
pid=<core-pid>
unix=<absolute-socket-path>   # Windows 或 UDS 回退时为 sock=<loopback-port>
EOF

信任模型

POSIX 使用 UDS(socket 文件位于 owner-only 私有 runtime 目录),port-file 以 0600 权限发布;loopback TCP 回退不隔离本机用户,能连接端口的本机进程可调用全部 RPC。

生命周期与会话路由

  • 状态机 NOT_READY → READY → STOPPING → CLOSED:SpinupService 幂等置 READY,READY 前 run 级 RPC 返回 FAILED_PRECONDITION;
  • DeliverRunStart 创建 UUIDv7 形式的 session_id(可按创建时间排序)存入并发安全 registry,run 级 RPC 按 session_id 路由;
  • session_id 只标识会话:每次调用创建独立会话,不按 run_id 去重。同一实验可能由多个进程上报(多 rank、resume 复用实验),各会话的缓冲与上传状态互不影响;
  • run_id 由 Python SDK 生成后透传,core 不补写;
  • ConfirmRunFinish 在对应会话的 DeliverRunFinish 交付后原子释放(提前调用返回 FAILED_PRECONDITION,会话保留),不关闭 gRPC Server;TeardownService 在独立 goroutine 执行 GracefulStop,避免 handler 内自等待;
  • Teardown、SIGINT/SIGTERM、父进程退出、Serve 异常汇入 controller 的关闭路径(GracefulStop + 超时强制 Stop 兜底);
  • 退出清理按 port-file 的 pid 字段判定归属,路径被后启动实例覆盖时不误删。

父进程监控

Linux 使用 PDEATHSIG 快路径,另有进程级兜底:先使用 pidfd,不可用时低频轮询 PID/PPID,覆盖从非主线程 spawn、spawn 线程先退出的场景;监控通知 controller,不在监控 goroutine 中执行关闭。

日志

--log-dir 可选开启 debug-core.log,为空时只写 stderr。轮转、stderr 合并与 skip_store 分流在后续提交接入。

detached 预留

--detach 与 --idle-timeout 为 detached 模式预留,当前版本接受传值但作为 no-op 忽略并记录 warning,core 仍以 Owner Mode 运行,当前实现暂不添加 detached 运行逻辑。

协议调整

core.proto 新增 Spinup/Teardown RPC;DeliverRunStart 响应返回 session_id,upsert/finish/stats/confirm 等 run 级请求携带该字段。原 run_handle 统一更名为 session_id,字段号保持不变;Python/Go stub 重新生成。

退出码

码 含义
0 正常退出(含信号触发的优雅关闭)
1 运行错误
2 用法错误
3 端点已被其它 core 占用,调用方应等待该实例就绪而不是重启

Go core 启动流程

Python SDK: subprocess.Popen(swanlab-core)
  │
  ▼
swanlab-core
  ├─ 选择端点                  # POSIX UDS(占用者存活则退出码 3,残留 socket 回收);Windows loopback TCP
  ├─ 建立父进程退出监控         # PDEATHSIG 快路径 + pidfd/PPID 轮询兜底
  ├─ 创建 gRPC Server          # NOT_READY
  └─ 原子写 port-file          # protocol/pid/endpoint,chmod 0600 + rename
       │
       ▼  Python 读 port-file → channel ready → SpinupService
  NOT_READY → READY            # 幂等;READY 前 run RPC 返回 FAILED_PRECONDITION
       │
       ▼  DeliverRunStart(start_record, core_settings)
  创建 session_id + Session    # UUIDv7,run 级 RPC 按 session_id 路由

一次 run 上报结束(ConfirmRunFinish)与服务退出(TeardownService)在协议层分离

Python SDK 客户端(CoreGo)

由 #1803 合入。

swanlab/sdk/internal/core_go/
├── discovery/   # port-file v1 解析、pid / UDS 归属校验
├── process/     # runtime 目录、spawn、wait → terminate → kill 回收
├── rpc/         # 延迟 import grpc;Spinup / DeliverRunStart / ... 封装
├── launcher.py  # CoreLauncher:start / close / abort
└── core.py      # CoreGo:状态机、session 绑定、fork

握手与关闭

CoreGo.deliver_run_start()
  → CoreLauncher.start()   # runtime → spawn → wait_endpoint → channel ready
  → SpinupService → DeliverRunStart → 绑定 session_id(RUNNING)

CoreGo.close() / abort()
  → TeardownService(尽力)→ Channel.close() → reap_process() → cleanup_runtime()

行为与边界

  • 状态机 NEW → STARTING → RUNNING → FINISHED → CLOSED,启动失败走 ABORTING;close() / abort() 幂等。
  • Upsert 数据面 Go 侧尚未实现,返回 UNIMPLEMENTED:客户端只记一次 warning,不重试、不改写、不回退 CorePython。
  • CoreGo 当前只实现 online;local / offline 返回 MODE_UNSUPPORTED,disabled 为 no-op。CorePython 的四种模式不变。
  • create_core() 未改动,生产路径仍走 CorePython;CoreEnum.CORE → CoreGo 的切换在后续切片。
  • fork:core.fork() 返回 no-op 的 _DetachedCoreGo(继承 NullCoreProtocol),不持有父进程资源;仅覆盖显式 os.fork(),macOS 默认 spawn 需在子进程重新 swanlab.init()。

测试

  • tests/unit/sdk/internal/core_go/ 覆盖发现、进程、RPC、launcher 与错误映射,110 passed;全量 pytest tests/unit 2139 passed / 21 skipped,ruff / basedpyright 通过。
  • 真实二进制 smoke 本地验证、不入库:UDS happy path、TCP 回退、abort、binary 缺失、UNIMPLEMENTED、重复 close。

风险提示

Go core 以 CGO_ENABLED=0 静态编译,二进制无动态链接依赖,不要求宿主机的 glibc 版本。发行版暂不支持 alpine,是 Python 侧 grpcio 需要 glibc,与 core 二进制无关。

@Nexisato Nexisato self-assigned this Sep 20, 2026
@Nexisato Nexisato added the 💪 enhancement New feature or request label Sep 20, 2026
@Nexisato
Nexisato force-pushed the feat/gateway-starter branch from 5d35dc1 to c933966 Compare September 22, 2026 08:25
@Nexisato
Nexisato marked this pull request as ready for review September 23, 2026 07:16
@Nexisato
Nexisato force-pushed the feat/gateway-starter branch from 5b01a65 to e066548 Compare October 27, 2026 02:27
@Nexisato
Nexisato marked this pull request as draft October 27, 2026 05:58
@Nexisato
Nexisato force-pushed the feat/gateway-starter branch from 2885114 to f26a9b5 Compare October 27, 2026 06:46
@Nexisato
Nexisato force-pushed the feat/gateway-starter branch from f26a9b5 to 6960fb3 Compare October 27, 2026 06:50
@Nexisato Nexisato added the 🆕 version SwanLab new version label Oct 27, 2026
* feat: add python client skeleton

* feat(core-go): add protocol errors and port-file discovery

- add fixed handshake constants shared by client modules
- add CoreGoError with stage/reason and gRPC status-name mapping
- parse port-file v1 with Go portinfo parity
- validate pid and unix/tcp endpoint ownership

* feat(core-go): manage runtime, spawn and reap core process

- create private 0700 runtime dirs under the system temp root
- spawn swanlab-core with port-file/parent-pid only and sanitized env
- poll endpoint, classify early exit codes and validate ownership
- reap with wait -> terminate -> kill and keep bounded stderr

* feat(core-go): add grpc channel and CoreService rpc wrapper

- lazily import grpcio and wait for channel readiness
- map gRPC statuses once and preserve UNIMPLEMENTED details
- attach session_id to every run-level request
- convert success=false responses into protocol errors

* fix(core-go): use absolute imports and shared rpc channel type

- add RpcChannelType Protocol under swanlab.sdk.typings.core_go
- switch core_go modules to absolute imports and drop the rpc re-export
- report missing grpcio as GRPC_UNAVAILABLE instead of leaking ImportError
- skip POSIX mode/uid checks on Windows runtime dirs

* feat(core-go): own core process lifecycle in launcher

- add CoreLauncher as the sole owner of runtime, process and channel
- validate binary before creating runtime and clean up on any start failure
- make close/abort idempotent and guard fork children via pkg.fork
- reuse validate_core_binary and cover launcher orchestration with unit tests

* feat(core-go): bind run session in CoreGo

- add CoreGo state machine and single session_id binding
- start via launcher, SpinupService and DeliverRunStart
- forward upserts and surface UNIMPLEMENTED once without fallback
- map finish/stats/confirm and keep close/abort idempotent
- return a NullCoreProtocol-based detached stub for fork children

* fix(test): fork error on win32 platform

* fix(core-go): resolve binary path

Resolve core binary paths before launching from the runtime directory, and use safe.block to contain cleanup failures during teardown and channel close. Add a regression test for relative binary paths.
@Nexisato Nexisato changed the title Feat/gateway starter Feat/port-file-starter-protocol Oct 28, 2026
@Nexisato
Nexisato marked this pull request as ready for review October 28, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💪 enhancement New feature or request 🆕 version SwanLab new version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant