From 50b881a11c7859aa9bc9685b71a9e642046e2aed Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Fri, 18 Sep 2026 17:04:36 +0800 Subject: [PATCH 01/15] feat: add v2 core proto --- core/proto/swanlab/grpc/core/v2/core.pb.go | 1191 +++++++++++++++++ .../swanlab/grpc/core/v2/core_grpc.pb.go | 546 ++++++++ protos/swanlab/grpc/core/v2/core.proto | 170 +++ .../proto/swanlab/grpc/core/v2/__init__.py | 0 .../proto/swanlab/grpc/core/v2/core_pb2.py | 79 ++ .../proto/swanlab/grpc/core/v2/core_pb2.pyi | 167 +++ .../swanlab/grpc/core/v2/core_pb2_grpc.py | 567 ++++++++ 7 files changed, 2720 insertions(+) create mode 100644 core/proto/swanlab/grpc/core/v2/core.pb.go create mode 100644 core/proto/swanlab/grpc/core/v2/core_grpc.pb.go create mode 100644 protos/swanlab/grpc/core/v2/core.proto create mode 100644 swanlab/proto/swanlab/grpc/core/v2/__init__.py create mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2.py create mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi create mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py diff --git a/core/proto/swanlab/grpc/core/v2/core.pb.go b/core/proto/swanlab/grpc/core/v2/core.pb.go new file mode 100644 index 000000000..a2d225cd8 --- /dev/null +++ b/core/proto/swanlab/grpc/core/v2/core.pb.go @@ -0,0 +1,1191 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.35.1 +// source: swanlab/grpc/core/v2/core.proto + +package corev2 + +import ( + v12 "github.com/swanhubx/swanlab/core/proto/swanlab/metric/column/v1" + v13 "github.com/swanhubx/swanlab/core/proto/swanlab/metric/data/v1" + v16 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" + v11 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" + v15 "github.com/swanhubx/swanlab/core/proto/swanlab/save/v1" + v1 "github.com/swanhubx/swanlab/core/proto/swanlab/settings/core/v1" + v14 "github.com/swanhubx/swanlab/core/proto/swanlab/terminal/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// GetCapabilities 请求。当前无字段,预留后续扩展。 +type GetCapabilitiesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCapabilitiesRequest) Reset() { + *x = GetCapabilitiesRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCapabilitiesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCapabilitiesRequest) ProtoMessage() {} + +func (x *GetCapabilitiesRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCapabilitiesRequest.ProtoReflect.Descriptor instead. +func (*GetCapabilitiesRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{0} +} + +// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, +// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 +type GetCapabilitiesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProtocolVersion string `protobuf:"bytes,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` // 协议版本,与 Python SDK 期望版本锁步 + CoreVersion string `protobuf:"bytes,2,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` // core 二进制编译版本(构建管线注入) + Lifecycle bool `protobuf:"varint,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"` // 生命周期与进程管理能力 + SupportedModes []string `protobuf:"bytes,4,rep,name=supported_modes,json=supportedModes,proto3" json:"supported_modes,omitempty"` // 支持的 run 模式,如 online/local/offline + Store bool `protobuf:"varint,5,opt,name=store,proto3" json:"store,omitempty"` // 本地持久化能力 + Transport bool `protobuf:"varint,6,opt,name=transport,proto3" json:"transport,omitempty"` // 云端上传能力 + Payload bool `protobuf:"varint,7,opt,name=payload,proto3" json:"payload,omitempty"` // 内联 payload 能力(online skip_store 模式) + Sync bool `protobuf:"varint,8,opt,name=sync,proto3" json:"sync,omitempty"` // 离线数据同步能力 + MaxReceiveMessageBytes int64 `protobuf:"varint,9,opt,name=max_receive_message_bytes,json=maxReceiveMessageBytes,proto3" json:"max_receive_message_bytes,omitempty"` // 服务端单条消息接收上限,用于客户端协商 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCapabilitiesResponse) Reset() { + *x = GetCapabilitiesResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCapabilitiesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCapabilitiesResponse) ProtoMessage() {} + +func (x *GetCapabilitiesResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCapabilitiesResponse.ProtoReflect.Descriptor instead. +func (*GetCapabilitiesResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCapabilitiesResponse) GetProtocolVersion() string { + if x != nil { + return x.ProtocolVersion + } + return "" +} + +func (x *GetCapabilitiesResponse) GetCoreVersion() string { + if x != nil { + return x.CoreVersion + } + return "" +} + +func (x *GetCapabilitiesResponse) GetLifecycle() bool { + if x != nil { + return x.Lifecycle + } + return false +} + +func (x *GetCapabilitiesResponse) GetSupportedModes() []string { + if x != nil { + return x.SupportedModes + } + return nil +} + +func (x *GetCapabilitiesResponse) GetStore() bool { + if x != nil { + return x.Store + } + return false +} + +func (x *GetCapabilitiesResponse) GetTransport() bool { + if x != nil { + return x.Transport + } + return false +} + +func (x *GetCapabilitiesResponse) GetPayload() bool { + if x != nil { + return x.Payload + } + return false +} + +func (x *GetCapabilitiesResponse) GetSync() bool { + if x != nil { + return x.Sync + } + return false +} + +func (x *GetCapabilitiesResponse) GetMaxReceiveMessageBytes() int64 { + if x != nil { + return x.MaxReceiveMessageBytes + } + return 0 +} + +// TeardownService 请求。 +type TeardownServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TeardownServiceRequest) Reset() { + *x = TeardownServiceRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TeardownServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownServiceRequest) ProtoMessage() {} + +func (x *TeardownServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownServiceRequest.ProtoReflect.Descriptor instead. +func (*TeardownServiceRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{2} +} + +func (x *TeardownServiceRequest) GetOwnerToken() string { + if x != nil { + return x.OwnerToken + } + return "" +} + +// TeardownService 响应。 +type TeardownServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TeardownServiceResponse) Reset() { + *x = TeardownServiceResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TeardownServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownServiceResponse) ProtoMessage() {} + +func (x *TeardownServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownServiceResponse.ProtoReflect.Descriptor instead. +func (*TeardownServiceResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{3} +} + +// DeliverRunStart 请求,生命周期函数,运行正式开始 +type DeliverRunStartRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CoreSettings *v1.CoreSettings `protobuf:"bytes,1,opt,name=core_settings,json=coreSettings,proto3" json:"core_settings,omitempty"` // Run 服务配置 + StartRecord *v11.StartRecord `protobuf:"bytes,2,opt,name=start_record,json=startRecord,proto3" json:"start_record,omitempty"` // Run 创建记录 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverRunStartRequest) Reset() { + *x = DeliverRunStartRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverRunStartRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverRunStartRequest) ProtoMessage() {} + +func (x *DeliverRunStartRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverRunStartRequest.ProtoReflect.Descriptor instead. +func (*DeliverRunStartRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{4} +} + +func (x *DeliverRunStartRequest) GetCoreSettings() *v1.CoreSettings { + if x != nil { + return x.CoreSettings + } + return nil +} + +func (x *DeliverRunStartRequest) GetStartRecord() *v11.StartRecord { + if x != nil { + return x.StartRecord + } + return nil +} + +// DeliverRunStart 响应 +type DeliverRunStartResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 + Run *v11.StartRecord `protobuf:"bytes,3,opt,name=run,proto3" json:"run,omitempty"` // 最终创建的 Run 记录 + Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` // 对应的实验名称 + GlobalStep int64 `protobuf:"varint,6,opt,name=global_step,json=globalStep,proto3" json:"global_step,omitempty"` // 起始全局步数 + GlobalSystemStep int64 `protobuf:"varint,7,opt,name=global_system_step,json=globalSystemStep,proto3" json:"global_system_step,omitempty"` // 起始全局系统步数 + NewExperiment bool `protobuf:"varint,8,opt,name=new_experiment,json=newExperiment,proto3" json:"new_experiment,omitempty"` // 是否是新实验 + RunHandle string `protobuf:"bytes,9,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverRunStartResponse) Reset() { + *x = DeliverRunStartResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverRunStartResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverRunStartResponse) ProtoMessage() {} + +func (x *DeliverRunStartResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverRunStartResponse.ProtoReflect.Descriptor instead. +func (*DeliverRunStartResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{5} +} + +func (x *DeliverRunStartResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *DeliverRunStartResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DeliverRunStartResponse) GetRun() *v11.StartRecord { + if x != nil { + return x.Run + } + return nil +} + +func (x *DeliverRunStartResponse) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *DeliverRunStartResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeliverRunStartResponse) GetGlobalStep() int64 { + if x != nil { + return x.GlobalStep + } + return 0 +} + +func (x *DeliverRunStartResponse) GetGlobalSystemStep() int64 { + if x != nil { + return x.GlobalSystemStep + } + return 0 +} + +func (x *DeliverRunStartResponse) GetNewExperiment() bool { + if x != nil { + return x.NewExperiment + } + return false +} + +func (x *DeliverRunStartResponse) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// UpsertColumns 请求,异步插入一批列定义 +type UpsertColumnsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Columns []*v12.ColumnRecord `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` // 列定义列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertColumnsRequest) Reset() { + *x = UpsertColumnsRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertColumnsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertColumnsRequest) ProtoMessage() {} + +func (x *UpsertColumnsRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertColumnsRequest.ProtoReflect.Descriptor instead. +func (*UpsertColumnsRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{6} +} + +func (x *UpsertColumnsRequest) GetColumns() []*v12.ColumnRecord { + if x != nil { + return x.Columns + } + return nil +} + +func (x *UpsertColumnsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// UpsertScalars 请求,异步插入一批标量数据 +type UpsertScalarsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []*v13.ScalarRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 标量记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertScalarsRequest) Reset() { + *x = UpsertScalarsRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertScalarsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertScalarsRequest) ProtoMessage() {} + +func (x *UpsertScalarsRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertScalarsRequest.ProtoReflect.Descriptor instead. +func (*UpsertScalarsRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{7} +} + +func (x *UpsertScalarsRequest) GetData() []*v13.ScalarRecord { + if x != nil { + return x.Data + } + return nil +} + +func (x *UpsertScalarsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// UpsertMedia 请求,异步插入一批媒体数据 +type UpsertMediaRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []*v13.MediaRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 媒体记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertMediaRequest) Reset() { + *x = UpsertMediaRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertMediaRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertMediaRequest) ProtoMessage() {} + +func (x *UpsertMediaRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertMediaRequest.ProtoReflect.Descriptor instead. +func (*UpsertMediaRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{8} +} + +func (x *UpsertMediaRequest) GetData() []*v13.MediaRecord { + if x != nil { + return x.Data + } + return nil +} + +func (x *UpsertMediaRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// UpsertLogs 请求,异步插入一批终端输出 +type UpsertLogsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Logs []*v14.LogRecord `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` // 终端日志记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertLogsRequest) Reset() { + *x = UpsertLogsRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertLogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertLogsRequest) ProtoMessage() {} + +func (x *UpsertLogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertLogsRequest.ProtoReflect.Descriptor instead. +func (*UpsertLogsRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{9} +} + +func (x *UpsertLogsRequest) GetLogs() []*v14.LogRecord { + if x != nil { + return x.Logs + } + return nil +} + +func (x *UpsertLogsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// UpsertSaves 请求,异步插入一批文件保存记录 +type UpsertSavesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Saves []*v15.SaveRecord `protobuf:"bytes,1,rep,name=saves,proto3" json:"saves,omitempty"` // 保存记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertSavesRequest) Reset() { + *x = UpsertSavesRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertSavesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertSavesRequest) ProtoMessage() {} + +func (x *UpsertSavesRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertSavesRequest.ProtoReflect.Descriptor instead. +func (*UpsertSavesRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{10} +} + +func (x *UpsertSavesRequest) GetSaves() []*v15.SaveRecord { + if x != nil { + return x.Saves + } + return nil +} + +func (x *UpsertSavesRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// DeliverRunFinish 请求,生命周期函数,运行结束 +type DeliverRunFinishRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + FinishRecord *v11.FinishRecord `protobuf:"bytes,1,opt,name=finish_record,json=finishRecord,proto3" json:"finish_record,omitempty"` // Run 结束记录 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverRunFinishRequest) Reset() { + *x = DeliverRunFinishRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverRunFinishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverRunFinishRequest) ProtoMessage() {} + +func (x *DeliverRunFinishRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverRunFinishRequest.ProtoReflect.Descriptor instead. +func (*DeliverRunFinishRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{11} +} + +func (x *DeliverRunFinishRequest) GetFinishRecord() *v11.FinishRecord { + if x != nil { + return x.FinishRecord + } + return nil +} + +func (x *DeliverRunFinishRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// DeliverRunFinish 响应 +type DeliverRunFinishResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeliverRunFinishResponse) Reset() { + *x = DeliverRunFinishResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeliverRunFinishResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeliverRunFinishResponse) ProtoMessage() {} + +func (x *DeliverRunFinishResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeliverRunFinishResponse.ProtoReflect.Descriptor instead. +func (*DeliverRunFinishResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{12} +} + +func (x *DeliverRunFinishResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *DeliverRunFinishResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// GetOperationStats 请求。 +type GetOperationStatsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOperationStatsRequest) Reset() { + *x = GetOperationStatsRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOperationStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOperationStatsRequest) ProtoMessage() {} + +func (x *GetOperationStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOperationStatsRequest.ProtoReflect.Descriptor instead. +func (*GetOperationStatsRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{13} +} + +func (x *GetOperationStatsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// GetOperationStats 响应 +type GetOperationStatsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 + Stats *v16.OperationStats `protobuf:"bytes,3,opt,name=stats,proto3" json:"stats,omitempty"` // Core 当前运行状态和上传进度快照 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOperationStatsResponse) Reset() { + *x = GetOperationStatsResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOperationStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOperationStatsResponse) ProtoMessage() {} + +func (x *GetOperationStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOperationStatsResponse.ProtoReflect.Descriptor instead. +func (*GetOperationStatsResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{14} +} + +func (x *GetOperationStatsResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *GetOperationStatsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *GetOperationStatsResponse) GetStats() *v16.OperationStats { + if x != nil { + return x.Stats + } + return nil +} + +// ConfirmRunFinish 请求。 +type ConfirmRunFinishRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmRunFinishRequest) Reset() { + *x = ConfirmRunFinishRequest{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmRunFinishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmRunFinishRequest) ProtoMessage() {} + +func (x *ConfirmRunFinishRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmRunFinishRequest.ProtoReflect.Descriptor instead. +func (*ConfirmRunFinishRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{15} +} + +func (x *ConfirmRunFinishRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + +// ConfirmRunFinish 响应 +type ConfirmRunFinishResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmRunFinishResponse) Reset() { + *x = ConfirmRunFinishResponse{} + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmRunFinishResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmRunFinishResponse) ProtoMessage() {} + +func (x *ConfirmRunFinishResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmRunFinishResponse.ProtoReflect.Descriptor instead. +func (*ConfirmRunFinishResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{16} +} + +func (x *ConfirmRunFinishResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *ConfirmRunFinishResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_swanlab_grpc_core_v2_core_proto protoreflect.FileDescriptor + +const file_swanlab_grpc_core_v2_core_proto_rawDesc = "" + + "\n" + + "\x1fswanlab/grpc/core/v2/core.proto\x12\x14swanlab.grpc.core.v2\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n" + + "\x16GetCapabilitiesRequest\"\xcb\x02\n" + + "\x17GetCapabilitiesResponse\x12)\n" + + "\x10protocol_version\x18\x01 \x01(\tR\x0fprotocolVersion\x12!\n" + + "\fcore_version\x18\x02 \x01(\tR\vcoreVersion\x12\x1c\n" + + "\tlifecycle\x18\x03 \x01(\bR\tlifecycle\x12'\n" + + "\x0fsupported_modes\x18\x04 \x03(\tR\x0esupportedModes\x12\x14\n" + + "\x05store\x18\x05 \x01(\bR\x05store\x12\x1c\n" + + "\ttransport\x18\x06 \x01(\bR\ttransport\x12\x18\n" + + "\apayload\x18\a \x01(\bR\apayload\x12\x12\n" + + "\x04sync\x18\b \x01(\bR\x04sync\x129\n" + + "\x19max_receive_message_bytes\x18\t \x01(\x03R\x16maxReceiveMessageBytes\"9\n" + + "\x16TeardownServiceRequest\x12\x1f\n" + + "\vowner_token\x18\x01 \x01(\tR\n" + + "ownerToken\"\x19\n" + + "\x17TeardownServiceResponse\"\xa5\x01\n" + + "\x16DeliverRunStartRequest\x12K\n" + + "\rcore_settings\x18\x01 \x01(\v2&.swanlab.settings.core.v1.CoreSettingsR\fcoreSettings\x12>\n" + + "\fstart_record\x18\x02 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\vstartRecord\"\xb9\x02\n" + + "\x17DeliverRunStartResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\x12-\n" + + "\x03run\x18\x03 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\x03run\x12\x12\n" + + "\x04path\x18\x04 \x01(\tR\x04path\x12\x12\n" + + "\x04name\x18\x05 \x01(\tR\x04name\x12\x1f\n" + + "\vglobal_step\x18\x06 \x01(\x03R\n" + + "globalStep\x12,\n" + + "\x12global_system_step\x18\a \x01(\x03R\x10globalSystemStep\x12%\n" + + "\x0enew_experiment\x18\b \x01(\bR\rnewExperiment\x12\x1d\n" + + "\n" + + "run_handle\x18\t \x01(\tR\trunHandle\"w\n" + + "\x14UpsertColumnsRequest\x12@\n" + + "\acolumns\x18\x01 \x03(\v2&.swanlab.metric.column.v1.ColumnRecordR\acolumns\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"o\n" + + "\x14UpsertScalarsRequest\x128\n" + + "\x04data\x18\x01 \x03(\v2$.swanlab.metric.data.v1.ScalarRecordR\x04data\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"l\n" + + "\x12UpsertMediaRequest\x127\n" + + "\x04data\x18\x01 \x03(\v2#.swanlab.metric.data.v1.MediaRecordR\x04data\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + + "\x11UpsertLogsRequest\x122\n" + + "\x04logs\x18\x01 \x03(\v2\x1e.swanlab.terminal.v1.LogRecordR\x04logs\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + + "\x12UpsertSavesRequest\x121\n" + + "\x05saves\x18\x01 \x03(\v2\x1b.swanlab.save.v1.SaveRecordR\x05saves\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"{\n" + + "\x17DeliverRunFinishRequest\x12A\n" + + "\rfinish_record\x18\x01 \x01(\v2\x1c.swanlab.run.v1.FinishRecordR\ffinishRecord\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"N\n" + + "\x18DeliverRunFinishResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + + "\x18GetOperationStatsRequest\x12\x1d\n" + + "\n" + + "run_handle\x18\x01 \x01(\tR\trunHandle\"\x8b\x01\n" + + "\x19GetOperationStatsResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\x12:\n" + + "\x05stats\x18\x03 \x01(\v2$.swanlab.operation.v1.OperationStatsR\x05stats\"8\n" + + "\x17ConfirmRunFinishRequest\x12\x1d\n" + + "\n" + + "run_handle\x18\x01 \x01(\tR\trunHandle\"N\n" + + "\x18ConfirmRunFinishResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage2\xd4\b\n" + + "\vCoreService\x12n\n" + + "\x0fGetCapabilities\x12,.swanlab.grpc.core.v2.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v2.GetCapabilitiesResponse\x12n\n" + + "\x0fTeardownService\x12,.swanlab.grpc.core.v2.TeardownServiceRequest\x1a-.swanlab.grpc.core.v2.TeardownServiceResponse\x12n\n" + + "\x0fDeliverRunStart\x12,.swanlab.grpc.core.v2.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v2.DeliverRunStartResponse\x12S\n" + + "\rUpsertColumns\x12*.swanlab.grpc.core.v2.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n" + + "\rUpsertScalars\x12*.swanlab.grpc.core.v2.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + + "\vUpsertMedia\x12(.swanlab.grpc.core.v2.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n" + + "\n" + + "UpsertLogs\x12'.swanlab.grpc.core.v2.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + + "\vUpsertSaves\x12(.swanlab.grpc.core.v2.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n" + + "\x10DeliverRunFinish\x12-.swanlab.grpc.core.v2.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v2.DeliverRunFinishResponse\x12t\n" + + "\x11GetOperationStats\x12..swanlab.grpc.core.v2.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v2.GetOperationStatsResponse\x12q\n" + + "\x10ConfirmRunFinish\x12-.swanlab.grpc.core.v2.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v2.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2b\x06proto3" + +var ( + file_swanlab_grpc_core_v2_core_proto_rawDescOnce sync.Once + file_swanlab_grpc_core_v2_core_proto_rawDescData []byte +) + +func file_swanlab_grpc_core_v2_core_proto_rawDescGZIP() []byte { + file_swanlab_grpc_core_v2_core_proto_rawDescOnce.Do(func() { + file_swanlab_grpc_core_v2_core_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_swanlab_grpc_core_v2_core_proto_rawDesc), len(file_swanlab_grpc_core_v2_core_proto_rawDesc))) + }) + return file_swanlab_grpc_core_v2_core_proto_rawDescData +} + +var file_swanlab_grpc_core_v2_core_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_swanlab_grpc_core_v2_core_proto_goTypes = []any{ + (*GetCapabilitiesRequest)(nil), // 0: swanlab.grpc.core.v2.GetCapabilitiesRequest + (*GetCapabilitiesResponse)(nil), // 1: swanlab.grpc.core.v2.GetCapabilitiesResponse + (*TeardownServiceRequest)(nil), // 2: swanlab.grpc.core.v2.TeardownServiceRequest + (*TeardownServiceResponse)(nil), // 3: swanlab.grpc.core.v2.TeardownServiceResponse + (*DeliverRunStartRequest)(nil), // 4: swanlab.grpc.core.v2.DeliverRunStartRequest + (*DeliverRunStartResponse)(nil), // 5: swanlab.grpc.core.v2.DeliverRunStartResponse + (*UpsertColumnsRequest)(nil), // 6: swanlab.grpc.core.v2.UpsertColumnsRequest + (*UpsertScalarsRequest)(nil), // 7: swanlab.grpc.core.v2.UpsertScalarsRequest + (*UpsertMediaRequest)(nil), // 8: swanlab.grpc.core.v2.UpsertMediaRequest + (*UpsertLogsRequest)(nil), // 9: swanlab.grpc.core.v2.UpsertLogsRequest + (*UpsertSavesRequest)(nil), // 10: swanlab.grpc.core.v2.UpsertSavesRequest + (*DeliverRunFinishRequest)(nil), // 11: swanlab.grpc.core.v2.DeliverRunFinishRequest + (*DeliverRunFinishResponse)(nil), // 12: swanlab.grpc.core.v2.DeliverRunFinishResponse + (*GetOperationStatsRequest)(nil), // 13: swanlab.grpc.core.v2.GetOperationStatsRequest + (*GetOperationStatsResponse)(nil), // 14: swanlab.grpc.core.v2.GetOperationStatsResponse + (*ConfirmRunFinishRequest)(nil), // 15: swanlab.grpc.core.v2.ConfirmRunFinishRequest + (*ConfirmRunFinishResponse)(nil), // 16: swanlab.grpc.core.v2.ConfirmRunFinishResponse + (*v1.CoreSettings)(nil), // 17: swanlab.settings.core.v1.CoreSettings + (*v11.StartRecord)(nil), // 18: swanlab.run.v1.StartRecord + (*v12.ColumnRecord)(nil), // 19: swanlab.metric.column.v1.ColumnRecord + (*v13.ScalarRecord)(nil), // 20: swanlab.metric.data.v1.ScalarRecord + (*v13.MediaRecord)(nil), // 21: swanlab.metric.data.v1.MediaRecord + (*v14.LogRecord)(nil), // 22: swanlab.terminal.v1.LogRecord + (*v15.SaveRecord)(nil), // 23: swanlab.save.v1.SaveRecord + (*v11.FinishRecord)(nil), // 24: swanlab.run.v1.FinishRecord + (*v16.OperationStats)(nil), // 25: swanlab.operation.v1.OperationStats + (*emptypb.Empty)(nil), // 26: google.protobuf.Empty +} +var file_swanlab_grpc_core_v2_core_proto_depIdxs = []int32{ + 17, // 0: swanlab.grpc.core.v2.DeliverRunStartRequest.core_settings:type_name -> swanlab.settings.core.v1.CoreSettings + 18, // 1: swanlab.grpc.core.v2.DeliverRunStartRequest.start_record:type_name -> swanlab.run.v1.StartRecord + 18, // 2: swanlab.grpc.core.v2.DeliverRunStartResponse.run:type_name -> swanlab.run.v1.StartRecord + 19, // 3: swanlab.grpc.core.v2.UpsertColumnsRequest.columns:type_name -> swanlab.metric.column.v1.ColumnRecord + 20, // 4: swanlab.grpc.core.v2.UpsertScalarsRequest.data:type_name -> swanlab.metric.data.v1.ScalarRecord + 21, // 5: swanlab.grpc.core.v2.UpsertMediaRequest.data:type_name -> swanlab.metric.data.v1.MediaRecord + 22, // 6: swanlab.grpc.core.v2.UpsertLogsRequest.logs:type_name -> swanlab.terminal.v1.LogRecord + 23, // 7: swanlab.grpc.core.v2.UpsertSavesRequest.saves:type_name -> swanlab.save.v1.SaveRecord + 24, // 8: swanlab.grpc.core.v2.DeliverRunFinishRequest.finish_record:type_name -> swanlab.run.v1.FinishRecord + 25, // 9: swanlab.grpc.core.v2.GetOperationStatsResponse.stats:type_name -> swanlab.operation.v1.OperationStats + 0, // 10: swanlab.grpc.core.v2.CoreService.GetCapabilities:input_type -> swanlab.grpc.core.v2.GetCapabilitiesRequest + 2, // 11: swanlab.grpc.core.v2.CoreService.TeardownService:input_type -> swanlab.grpc.core.v2.TeardownServiceRequest + 4, // 12: swanlab.grpc.core.v2.CoreService.DeliverRunStart:input_type -> swanlab.grpc.core.v2.DeliverRunStartRequest + 6, // 13: swanlab.grpc.core.v2.CoreService.UpsertColumns:input_type -> swanlab.grpc.core.v2.UpsertColumnsRequest + 7, // 14: swanlab.grpc.core.v2.CoreService.UpsertScalars:input_type -> swanlab.grpc.core.v2.UpsertScalarsRequest + 8, // 15: swanlab.grpc.core.v2.CoreService.UpsertMedia:input_type -> swanlab.grpc.core.v2.UpsertMediaRequest + 9, // 16: swanlab.grpc.core.v2.CoreService.UpsertLogs:input_type -> swanlab.grpc.core.v2.UpsertLogsRequest + 10, // 17: swanlab.grpc.core.v2.CoreService.UpsertSaves:input_type -> swanlab.grpc.core.v2.UpsertSavesRequest + 11, // 18: swanlab.grpc.core.v2.CoreService.DeliverRunFinish:input_type -> swanlab.grpc.core.v2.DeliverRunFinishRequest + 13, // 19: swanlab.grpc.core.v2.CoreService.GetOperationStats:input_type -> swanlab.grpc.core.v2.GetOperationStatsRequest + 15, // 20: swanlab.grpc.core.v2.CoreService.ConfirmRunFinish:input_type -> swanlab.grpc.core.v2.ConfirmRunFinishRequest + 1, // 21: swanlab.grpc.core.v2.CoreService.GetCapabilities:output_type -> swanlab.grpc.core.v2.GetCapabilitiesResponse + 3, // 22: swanlab.grpc.core.v2.CoreService.TeardownService:output_type -> swanlab.grpc.core.v2.TeardownServiceResponse + 5, // 23: swanlab.grpc.core.v2.CoreService.DeliverRunStart:output_type -> swanlab.grpc.core.v2.DeliverRunStartResponse + 26, // 24: swanlab.grpc.core.v2.CoreService.UpsertColumns:output_type -> google.protobuf.Empty + 26, // 25: swanlab.grpc.core.v2.CoreService.UpsertScalars:output_type -> google.protobuf.Empty + 26, // 26: swanlab.grpc.core.v2.CoreService.UpsertMedia:output_type -> google.protobuf.Empty + 26, // 27: swanlab.grpc.core.v2.CoreService.UpsertLogs:output_type -> google.protobuf.Empty + 26, // 28: swanlab.grpc.core.v2.CoreService.UpsertSaves:output_type -> google.protobuf.Empty + 12, // 29: swanlab.grpc.core.v2.CoreService.DeliverRunFinish:output_type -> swanlab.grpc.core.v2.DeliverRunFinishResponse + 14, // 30: swanlab.grpc.core.v2.CoreService.GetOperationStats:output_type -> swanlab.grpc.core.v2.GetOperationStatsResponse + 16, // 31: swanlab.grpc.core.v2.CoreService.ConfirmRunFinish:output_type -> swanlab.grpc.core.v2.ConfirmRunFinishResponse + 21, // [21:32] is the sub-list for method output_type + 10, // [10:21] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_swanlab_grpc_core_v2_core_proto_init() } +func file_swanlab_grpc_core_v2_core_proto_init() { + if File_swanlab_grpc_core_v2_core_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_swanlab_grpc_core_v2_core_proto_rawDesc), len(file_swanlab_grpc_core_v2_core_proto_rawDesc)), + NumEnums: 0, + NumMessages: 17, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_swanlab_grpc_core_v2_core_proto_goTypes, + DependencyIndexes: file_swanlab_grpc_core_v2_core_proto_depIdxs, + MessageInfos: file_swanlab_grpc_core_v2_core_proto_msgTypes, + }.Build() + File_swanlab_grpc_core_v2_core_proto = out.File + file_swanlab_grpc_core_v2_core_proto_goTypes = nil + file_swanlab_grpc_core_v2_core_proto_depIdxs = nil +} diff --git a/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go b/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go new file mode 100644 index 000000000..58c50e48c --- /dev/null +++ b/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go @@ -0,0 +1,546 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v7.35.1 +// source: swanlab/grpc/core/v2/core.proto + +package corev2 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CoreService_GetCapabilities_FullMethodName = "/swanlab.grpc.core.v2.CoreService/GetCapabilities" + CoreService_TeardownService_FullMethodName = "/swanlab.grpc.core.v2.CoreService/TeardownService" + CoreService_DeliverRunStart_FullMethodName = "/swanlab.grpc.core.v2.CoreService/DeliverRunStart" + CoreService_UpsertColumns_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertColumns" + CoreService_UpsertScalars_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertScalars" + CoreService_UpsertMedia_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertMedia" + CoreService_UpsertLogs_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertLogs" + CoreService_UpsertSaves_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertSaves" + CoreService_DeliverRunFinish_FullMethodName = "/swanlab.grpc.core.v2.CoreService/DeliverRunFinish" + CoreService_GetOperationStats_FullMethodName = "/swanlab.grpc.core.v2.CoreService/GetOperationStats" + CoreService_ConfirmRunFinish_FullMethodName = "/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish" +) + +// CoreServiceClient is the client API for CoreService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 +// +// 相对 v1 的关键变更: +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 +// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 +type CoreServiceClient interface { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) + // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 + DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) + // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 + UpsertColumns(ctx context.Context, in *UpsertColumnsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 + UpsertScalars(ctx context.Context, in *UpsertScalarsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 + UpsertMedia(ctx context.Context, in *UpsertMediaRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 + UpsertLogs(ctx context.Context, in *UpsertLogsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 + UpsertSaves(ctx context.Context, in *UpsertSavesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 + DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) +} + +type coreServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient { + return &coreServiceClient{cc} +} + +func (c *coreServiceClient) GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCapabilitiesResponse) + err := c.cc.Invoke(ctx, CoreService_GetCapabilities_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TeardownServiceResponse) + err := c.cc.Invoke(ctx, CoreService_TeardownService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeliverRunStartResponse) + err := c.cc.Invoke(ctx, CoreService_DeliverRunStart_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) UpsertColumns(ctx context.Context, in *UpsertColumnsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CoreService_UpsertColumns_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) UpsertScalars(ctx context.Context, in *UpsertScalarsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CoreService_UpsertScalars_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) UpsertMedia(ctx context.Context, in *UpsertMediaRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CoreService_UpsertMedia_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) UpsertLogs(ctx context.Context, in *UpsertLogsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CoreService_UpsertLogs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) UpsertSaves(ctx context.Context, in *UpsertSavesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CoreService_UpsertSaves_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeliverRunFinishResponse) + err := c.cc.Invoke(ctx, CoreService_DeliverRunFinish_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetOperationStatsResponse) + err := c.cc.Invoke(ctx, CoreService_GetOperationStats_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConfirmRunFinishResponse) + err := c.cc.Invoke(ctx, CoreService_ConfirmRunFinish_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CoreServiceServer is the server API for CoreService service. +// All implementations must embed UnimplementedCoreServiceServer +// for forward compatibility. +// +// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 +// +// 相对 v1 的关键变更: +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 +// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 +type CoreServiceServer interface { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) + // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 + DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) + // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 + UpsertColumns(context.Context, *UpsertColumnsRequest) (*emptypb.Empty, error) + // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 + UpsertScalars(context.Context, *UpsertScalarsRequest) (*emptypb.Empty, error) + // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 + UpsertMedia(context.Context, *UpsertMediaRequest) (*emptypb.Empty, error) + // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 + UpsertLogs(context.Context, *UpsertLogsRequest) (*emptypb.Empty, error) + // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 + UpsertSaves(context.Context, *UpsertSavesRequest) (*emptypb.Empty, error) + // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 + DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) + mustEmbedUnimplementedCoreServiceServer() +} + +// UnimplementedCoreServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCoreServiceServer struct{} + +func (UnimplementedCoreServiceServer) GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCapabilities not implemented") +} +func (UnimplementedCoreServiceServer) TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TeardownService not implemented") +} +func (UnimplementedCoreServiceServer) DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeliverRunStart not implemented") +} +func (UnimplementedCoreServiceServer) UpsertColumns(context.Context, *UpsertColumnsRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpsertColumns not implemented") +} +func (UnimplementedCoreServiceServer) UpsertScalars(context.Context, *UpsertScalarsRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpsertScalars not implemented") +} +func (UnimplementedCoreServiceServer) UpsertMedia(context.Context, *UpsertMediaRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpsertMedia not implemented") +} +func (UnimplementedCoreServiceServer) UpsertLogs(context.Context, *UpsertLogsRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpsertLogs not implemented") +} +func (UnimplementedCoreServiceServer) UpsertSaves(context.Context, *UpsertSavesRequest) (*emptypb.Empty, error) { + return nil, status.Error(codes.Unimplemented, "method UpsertSaves not implemented") +} +func (UnimplementedCoreServiceServer) DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeliverRunFinish not implemented") +} +func (UnimplementedCoreServiceServer) GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetOperationStats not implemented") +} +func (UnimplementedCoreServiceServer) ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ConfirmRunFinish not implemented") +} +func (UnimplementedCoreServiceServer) mustEmbedUnimplementedCoreServiceServer() {} +func (UnimplementedCoreServiceServer) testEmbeddedByValue() {} + +// UnsafeCoreServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CoreServiceServer will +// result in compilation errors. +type UnsafeCoreServiceServer interface { + mustEmbedUnimplementedCoreServiceServer() +} + +func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) { + // If the following call panics, it indicates UnimplementedCoreServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CoreService_ServiceDesc, srv) +} + +func _CoreService_GetCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCapabilitiesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).GetCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_GetCapabilities_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).GetCapabilities(ctx, req.(*GetCapabilitiesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_TeardownService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TeardownServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).TeardownService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_TeardownService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).TeardownService(ctx, req.(*TeardownServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_DeliverRunStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeliverRunStartRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).DeliverRunStart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_DeliverRunStart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).DeliverRunStart(ctx, req.(*DeliverRunStartRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_UpsertColumns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertColumnsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).UpsertColumns(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_UpsertColumns_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).UpsertColumns(ctx, req.(*UpsertColumnsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_UpsertScalars_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertScalarsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).UpsertScalars(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_UpsertScalars_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).UpsertScalars(ctx, req.(*UpsertScalarsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_UpsertMedia_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertMediaRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).UpsertMedia(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_UpsertMedia_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).UpsertMedia(ctx, req.(*UpsertMediaRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_UpsertLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertLogsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).UpsertLogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_UpsertLogs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).UpsertLogs(ctx, req.(*UpsertLogsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_UpsertSaves_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertSavesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).UpsertSaves(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_UpsertSaves_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).UpsertSaves(ctx, req.(*UpsertSavesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_DeliverRunFinish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeliverRunFinishRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).DeliverRunFinish(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_DeliverRunFinish_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).DeliverRunFinish(ctx, req.(*DeliverRunFinishRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_GetOperationStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOperationStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).GetOperationStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_GetOperationStats_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).GetOperationStats(ctx, req.(*GetOperationStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_ConfirmRunFinish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfirmRunFinishRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).ConfirmRunFinish(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_ConfirmRunFinish_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).ConfirmRunFinish(ctx, req.(*ConfirmRunFinishRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CoreService_ServiceDesc is the grpc.ServiceDesc for CoreService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CoreService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "swanlab.grpc.core.v2.CoreService", + HandlerType: (*CoreServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCapabilities", + Handler: _CoreService_GetCapabilities_Handler, + }, + { + MethodName: "TeardownService", + Handler: _CoreService_TeardownService_Handler, + }, + { + MethodName: "DeliverRunStart", + Handler: _CoreService_DeliverRunStart_Handler, + }, + { + MethodName: "UpsertColumns", + Handler: _CoreService_UpsertColumns_Handler, + }, + { + MethodName: "UpsertScalars", + Handler: _CoreService_UpsertScalars_Handler, + }, + { + MethodName: "UpsertMedia", + Handler: _CoreService_UpsertMedia_Handler, + }, + { + MethodName: "UpsertLogs", + Handler: _CoreService_UpsertLogs_Handler, + }, + { + MethodName: "UpsertSaves", + Handler: _CoreService_UpsertSaves_Handler, + }, + { + MethodName: "DeliverRunFinish", + Handler: _CoreService_DeliverRunFinish_Handler, + }, + { + MethodName: "GetOperationStats", + Handler: _CoreService_GetOperationStats_Handler, + }, + { + MethodName: "ConfirmRunFinish", + Handler: _CoreService_ConfirmRunFinish_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "swanlab/grpc/core/v2/core.proto", +} diff --git a/protos/swanlab/grpc/core/v2/core.proto b/protos/swanlab/grpc/core/v2/core.proto new file mode 100644 index 000000000..50a2e5b4b --- /dev/null +++ b/protos/swanlab/grpc/core/v2/core.proto @@ -0,0 +1,170 @@ +syntax = "proto3"; + +package swanlab.grpc.core.v2; + +import "google/protobuf/empty.proto"; +import "swanlab/run/v1/run.proto"; +import "swanlab/metric/data/v1/data.proto"; +import "swanlab/metric/column/v1/column.proto"; +import "swanlab/terminal/v1/log.proto"; +import "swanlab/save/v1/save.proto"; +import "swanlab/operation/v1/operation.proto"; +import "swanlab/settings/core/v1/core.proto"; + +option go_package = "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2"; + +// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 +// +// 相对 v1 的关键变更: +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 +// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 +service CoreService { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); + + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + rpc TeardownService(TeardownServiceRequest) returns (TeardownServiceResponse); + + // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 + rpc DeliverRunStart(DeliverRunStartRequest) returns (DeliverRunStartResponse); + + // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 + rpc UpsertColumns(UpsertColumnsRequest) returns (google.protobuf.Empty); + + // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 + rpc UpsertScalars(UpsertScalarsRequest) returns (google.protobuf.Empty); + + // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 + rpc UpsertMedia(UpsertMediaRequest) returns (google.protobuf.Empty); + + // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 + rpc UpsertLogs(UpsertLogsRequest) returns (google.protobuf.Empty); + + // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 + rpc UpsertSaves(UpsertSavesRequest) returns (google.protobuf.Empty); + + // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 + rpc DeliverRunFinish(DeliverRunFinishRequest) returns (DeliverRunFinishResponse); + + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + rpc GetOperationStats(GetOperationStatsRequest) returns (GetOperationStatsResponse); + + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + rpc ConfirmRunFinish(ConfirmRunFinishRequest) returns (ConfirmRunFinishResponse); +} + +// GetCapabilities 请求。当前无字段,预留后续扩展。 +message GetCapabilitiesRequest { +} + +// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, +// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 +message GetCapabilitiesResponse { + string protocol_version = 1; // 协议版本,与 Python SDK 期望版本锁步 + string core_version = 2; // core 二进制编译版本(构建管线注入) + bool lifecycle = 3; // 生命周期与进程管理能力 + repeated string supported_modes = 4; // 支持的 run 模式,如 online/local/offline + bool store = 5; // 本地持久化能力 + bool transport = 6; // 云端上传能力 + bool payload = 7; // 内联 payload 能力(online skip_store 模式) + bool sync = 8; // 离线数据同步能力 + int64 max_receive_message_bytes = 9; // 服务端单条消息接收上限,用于客户端协商 +} + +// TeardownService 请求。 +message TeardownServiceRequest { + string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 +} + +// TeardownService 响应。 +message TeardownServiceResponse { +} + +// DeliverRunStart 请求,生命周期函数,运行正式开始 +message DeliverRunStartRequest { + swanlab.settings.core.v1.CoreSettings core_settings = 1; // Run 服务配置 + swanlab.run.v1.StartRecord start_record = 2; // Run 创建记录 +} + +// DeliverRunStart 响应 +message DeliverRunStartResponse { + bool success = 1; // 请求是否成功 + string message = 2; // 请求失败的响应 + swanlab.run.v1.StartRecord run = 3; // 最终创建的 Run 记录 + string path = 4; // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) + string name = 5; // 对应的实验名称 + int64 global_step = 6; // 起始全局步数 + int64 global_system_step = 7; // 起始全局系统步数 + bool new_experiment = 8; // 是否是新实验 + string run_handle = 9; // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 +} + +// UpsertColumns 请求,异步插入一批列定义 +message UpsertColumnsRequest { + repeated swanlab.metric.column.v1.ColumnRecord columns = 1; // 列定义列表 + string run_handle = 2; // 目标 run 会话标识 +} + +// UpsertScalars 请求,异步插入一批标量数据 +message UpsertScalarsRequest { + repeated swanlab.metric.data.v1.ScalarRecord data = 1; // 标量记录列表 + string run_handle = 2; // 目标 run 会话标识 +} + +// UpsertMedia 请求,异步插入一批媒体数据 +message UpsertMediaRequest { + repeated swanlab.metric.data.v1.MediaRecord data = 1; // 媒体记录列表 + string run_handle = 2; // 目标 run 会话标识 +} + +// UpsertLogs 请求,异步插入一批终端输出 +message UpsertLogsRequest { + repeated swanlab.terminal.v1.LogRecord logs = 1; // 终端日志记录列表 + string run_handle = 2; // 目标 run 会话标识 +} + +// UpsertSaves 请求,异步插入一批文件保存记录 +message UpsertSavesRequest { + repeated swanlab.save.v1.SaveRecord saves = 1; // 保存记录列表 + string run_handle = 2; // 目标 run 会话标识 +} + +// DeliverRunFinish 请求,生命周期函数,运行结束 +message DeliverRunFinishRequest { + swanlab.run.v1.FinishRecord finish_record = 1; // Run 结束记录 + string run_handle = 2; // 目标 run 会话标识 +} + +// DeliverRunFinish 响应 +message DeliverRunFinishResponse { + bool success = 1; // 请求是否成功 + string message = 2; // 请求失败的响应 +} + +// GetOperationStats 请求。 +message GetOperationStatsRequest { + string run_handle = 1; // 目标 run 会话标识 +} + +// GetOperationStats 响应 +message GetOperationStatsResponse { + bool success = 1; // 请求是否成功 + string message = 2; // 请求失败的响应 + swanlab.operation.v1.OperationStats stats = 3; // Core 当前运行状态和上传进度快照 +} + +// ConfirmRunFinish 请求。 +message ConfirmRunFinishRequest { + string run_handle = 1; // 目标 run 会话标识 +} + +// ConfirmRunFinish 响应 +message ConfirmRunFinishResponse { + bool success = 1; // 请求是否成功 + string message = 2; // 请求失败的响应 +} diff --git a/swanlab/proto/swanlab/grpc/core/v2/__init__.py b/swanlab/proto/swanlab/grpc/core/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py new file mode 100644 index 000000000..4aa17082a --- /dev/null +++ b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: swanlab/grpc/core/v2/core.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'swanlab/grpc/core/v2/core.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +from swanlab.proto.swanlab.run.v1 import run_pb2 as swanlab_dot_run_dot_v1_dot_run__pb2 +from swanlab.proto.swanlab.metric.data.v1 import data_pb2 as swanlab_dot_metric_dot_data_dot_v1_dot_data__pb2 +from swanlab.proto.swanlab.metric.column.v1 import column_pb2 as swanlab_dot_metric_dot_column_dot_v1_dot_column__pb2 +from swanlab.proto.swanlab.terminal.v1 import log_pb2 as swanlab_dot_terminal_dot_v1_dot_log__pb2 +from swanlab.proto.swanlab.save.v1 import save_pb2 as swanlab_dot_save_dot_v1_dot_save__pb2 +from swanlab.proto.swanlab.operation.v1 import operation_pb2 as swanlab_dot_operation_dot_v1_dot_operation__pb2 +from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as swanlab_dot_settings_dot_core_dot_v1_dot_core__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v2/core.proto\x12\x14swanlab.grpc.core.v2\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n\x16GetCapabilitiesRequest\"\xd9\x01\n\x17GetCapabilitiesResponse\x12\x18\n\x10protocol_version\x18\x01 \x01(\t\x12\x14\n\x0c\x63ore_version\x18\x02 \x01(\t\x12\x11\n\tlifecycle\x18\x03 \x01(\x08\x12\x17\n\x0fsupported_modes\x18\x04 \x03(\t\x12\r\n\x05store\x18\x05 \x01(\x08\x12\x11\n\ttransport\x18\x06 \x01(\x08\x12\x0f\n\x07payload\x18\x07 \x01(\x08\x12\x0c\n\x04sync\x18\x08 \x01(\x08\x12!\n\x19max_receive_message_bytes\x18\t \x01(\x03\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xd4\x08\n\x0b\x43oreService\x12n\n\x0fGetCapabilities\x12,.swanlab.grpc.core.v2.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v2.GetCapabilitiesResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v2.TeardownServiceRequest\x1a-.swanlab.grpc.core.v2.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v2.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v2.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v2.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v2.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v2.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v2.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v2.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v2.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v2.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v2.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v2.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v2.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v2.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'swanlab.grpc.core.v2.core_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2' + _globals['_GETCAPABILITIESREQUEST']._serialized_start=320 + _globals['_GETCAPABILITIESREQUEST']._serialized_end=344 + _globals['_GETCAPABILITIESRESPONSE']._serialized_start=347 + _globals['_GETCAPABILITIESRESPONSE']._serialized_end=564 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=566 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=611 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=613 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=638 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=641 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=779 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=782 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=1004 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=1006 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=1105 + _globals['_UPSERTSCALARSREQUEST']._serialized_start=1107 + _globals['_UPSERTSCALARSREQUEST']._serialized_end=1201 + _globals['_UPSERTMEDIAREQUEST']._serialized_start=1203 + _globals['_UPSERTMEDIAREQUEST']._serialized_end=1294 + _globals['_UPSERTLOGSREQUEST']._serialized_start=1296 + _globals['_UPSERTLOGSREQUEST']._serialized_end=1381 + _globals['_UPSERTSAVESREQUEST']._serialized_start=1383 + _globals['_UPSERTSAVESREQUEST']._serialized_end=1467 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1469 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1567 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1569 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1629 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1631 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1677 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1679 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1793 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1795 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1840 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1842 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1902 + _globals['_CORESERVICE']._serialized_start=1905 + _globals['_CORESERVICE']._serialized_end=3013 +# @@protoc_insertion_point(module_scope) diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi new file mode 100644 index 000000000..7e4ca87c6 --- /dev/null +++ b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi @@ -0,0 +1,167 @@ +from google.protobuf import empty_pb2 as _empty_pb2 +from swanlab.proto.swanlab.run.v1 import run_pb2 as _run_pb2 +from swanlab.proto.swanlab.metric.data.v1 import data_pb2 as _data_pb2 +from swanlab.proto.swanlab.metric.column.v1 import column_pb2 as _column_pb2 +from swanlab.proto.swanlab.terminal.v1 import log_pb2 as _log_pb2 +from swanlab.proto.swanlab.save.v1 import save_pb2 as _save_pb2 +from swanlab.proto.swanlab.operation.v1 import operation_pb2 as _operation_pb2 +from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as _core_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class GetCapabilitiesRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCapabilitiesResponse(_message.Message): + __slots__ = ("protocol_version", "core_version", "lifecycle", "supported_modes", "store", "transport", "payload", "sync", "max_receive_message_bytes") + PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int] + CORE_VERSION_FIELD_NUMBER: _ClassVar[int] + LIFECYCLE_FIELD_NUMBER: _ClassVar[int] + SUPPORTED_MODES_FIELD_NUMBER: _ClassVar[int] + STORE_FIELD_NUMBER: _ClassVar[int] + TRANSPORT_FIELD_NUMBER: _ClassVar[int] + PAYLOAD_FIELD_NUMBER: _ClassVar[int] + SYNC_FIELD_NUMBER: _ClassVar[int] + MAX_RECEIVE_MESSAGE_BYTES_FIELD_NUMBER: _ClassVar[int] + protocol_version: str + core_version: str + lifecycle: bool + supported_modes: _containers.RepeatedScalarFieldContainer[str] + store: bool + transport: bool + payload: bool + sync: bool + max_receive_message_bytes: int + def __init__(self, protocol_version: _Optional[str] = ..., core_version: _Optional[str] = ..., lifecycle: bool = ..., supported_modes: _Optional[_Iterable[str]] = ..., store: bool = ..., transport: bool = ..., payload: bool = ..., sync: bool = ..., max_receive_message_bytes: _Optional[int] = ...) -> None: ... + +class TeardownServiceRequest(_message.Message): + __slots__ = ("owner_token",) + OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] + owner_token: str + def __init__(self, owner_token: _Optional[str] = ...) -> None: ... + +class TeardownServiceResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DeliverRunStartRequest(_message.Message): + __slots__ = ("core_settings", "start_record") + CORE_SETTINGS_FIELD_NUMBER: _ClassVar[int] + START_RECORD_FIELD_NUMBER: _ClassVar[int] + core_settings: _core_pb2.CoreSettings + start_record: _run_pb2.StartRecord + def __init__(self, core_settings: _Optional[_Union[_core_pb2.CoreSettings, _Mapping]] = ..., start_record: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ...) -> None: ... + +class DeliverRunStartResponse(_message.Message): + __slots__ = ("success", "message", "run", "path", "name", "global_step", "global_system_step", "new_experiment", "run_handle") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + RUN_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + GLOBAL_STEP_FIELD_NUMBER: _ClassVar[int] + GLOBAL_SYSTEM_STEP_FIELD_NUMBER: _ClassVar[int] + NEW_EXPERIMENT_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + success: bool + message: str + run: _run_pb2.StartRecord + path: str + name: str + global_step: int + global_system_step: int + new_experiment: bool + run_handle: str + def __init__(self, success: bool = ..., message: _Optional[str] = ..., run: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ..., path: _Optional[str] = ..., name: _Optional[str] = ..., global_step: _Optional[int] = ..., global_system_step: _Optional[int] = ..., new_experiment: bool = ..., run_handle: _Optional[str] = ...) -> None: ... + +class UpsertColumnsRequest(_message.Message): + __slots__ = ("columns", "run_handle") + COLUMNS_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + columns: _containers.RepeatedCompositeFieldContainer[_column_pb2.ColumnRecord] + run_handle: str + def __init__(self, columns: _Optional[_Iterable[_Union[_column_pb2.ColumnRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class UpsertScalarsRequest(_message.Message): + __slots__ = ("data", "run_handle") + DATA_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + data: _containers.RepeatedCompositeFieldContainer[_data_pb2.ScalarRecord] + run_handle: str + def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.ScalarRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class UpsertMediaRequest(_message.Message): + __slots__ = ("data", "run_handle") + DATA_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + data: _containers.RepeatedCompositeFieldContainer[_data_pb2.MediaRecord] + run_handle: str + def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.MediaRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class UpsertLogsRequest(_message.Message): + __slots__ = ("logs", "run_handle") + LOGS_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + logs: _containers.RepeatedCompositeFieldContainer[_log_pb2.LogRecord] + run_handle: str + def __init__(self, logs: _Optional[_Iterable[_Union[_log_pb2.LogRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class UpsertSavesRequest(_message.Message): + __slots__ = ("saves", "run_handle") + SAVES_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + saves: _containers.RepeatedCompositeFieldContainer[_save_pb2.SaveRecord] + run_handle: str + def __init__(self, saves: _Optional[_Iterable[_Union[_save_pb2.SaveRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class DeliverRunFinishRequest(_message.Message): + __slots__ = ("finish_record", "run_handle") + FINISH_RECORD_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + finish_record: _run_pb2.FinishRecord + run_handle: str + def __init__(self, finish_record: _Optional[_Union[_run_pb2.FinishRecord, _Mapping]] = ..., run_handle: _Optional[str] = ...) -> None: ... + +class DeliverRunFinishResponse(_message.Message): + __slots__ = ("success", "message") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + success: bool + message: str + def __init__(self, success: bool = ..., message: _Optional[str] = ...) -> None: ... + +class GetOperationStatsRequest(_message.Message): + __slots__ = ("run_handle",) + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + run_handle: str + def __init__(self, run_handle: _Optional[str] = ...) -> None: ... + +class GetOperationStatsResponse(_message.Message): + __slots__ = ("success", "message", "stats") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + STATS_FIELD_NUMBER: _ClassVar[int] + success: bool + message: str + stats: _operation_pb2.OperationStats + def __init__(self, success: bool = ..., message: _Optional[str] = ..., stats: _Optional[_Union[_operation_pb2.OperationStats, _Mapping]] = ...) -> None: ... + +class ConfirmRunFinishRequest(_message.Message): + __slots__ = ("run_handle",) + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + run_handle: str + def __init__(self, run_handle: _Optional[str] = ...) -> None: ... + +class ConfirmRunFinishResponse(_message.Message): + __slots__ = ("success", "message") + SUCCESS_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + success: bool + message: str + def __init__(self, success: bool = ..., message: _Optional[str] = ...) -> None: ... diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py b/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py new file mode 100644 index 000000000..b68fda345 --- /dev/null +++ b/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py @@ -0,0 +1,567 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +from swanlab.proto.swanlab.grpc.core.v2 import core_pb2 as swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2 + +GRPC_GENERATED_VERSION = '1.78.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in swanlab/grpc/core/v2/core_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class CoreServiceStub(object): + """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 + + 相对 v1 的关键变更: + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 + v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetCapabilities = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/GetCapabilities', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.FromString, + _registered_method=True) + self.TeardownService = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/TeardownService', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.FromString, + _registered_method=True) + self.DeliverRunStart = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/DeliverRunStart', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.FromString, + _registered_method=True) + self.UpsertColumns = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/UpsertColumns', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + self.UpsertScalars = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/UpsertScalars', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + self.UpsertMedia = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/UpsertMedia', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + self.UpsertLogs = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/UpsertLogs', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + self.UpsertSaves = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/UpsertSaves', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + self.DeliverRunFinish = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/DeliverRunFinish', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.FromString, + _registered_method=True) + self.GetOperationStats = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/GetOperationStats', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.FromString, + _registered_method=True) + self.ConfirmRunFinish = channel.unary_unary( + '/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish', + request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.FromString, + _registered_method=True) + + +class CoreServiceServicer(object): + """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 + + 相对 v1 的关键变更: + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 + v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 + """ + + def GetCapabilities(self, request, context): + """GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def TeardownService(self, request, context): + """TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeliverRunStart(self, request, context): + """DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpsertColumns(self, request, context): + """UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpsertScalars(self, request, context): + """UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpsertMedia(self, request, context): + """UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpsertLogs(self, request, context): + """UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpsertSaves(self, request, context): + """UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeliverRunFinish(self, request, context): + """DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetOperationStats(self, request, context): + """GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ConfirmRunFinish(self, request, context): + """ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CoreServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetCapabilities': grpc.unary_unary_rpc_method_handler( + servicer.GetCapabilities, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.SerializeToString, + ), + 'TeardownService': grpc.unary_unary_rpc_method_handler( + servicer.TeardownService, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.SerializeToString, + ), + 'DeliverRunStart': grpc.unary_unary_rpc_method_handler( + servicer.DeliverRunStart, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.SerializeToString, + ), + 'UpsertColumns': grpc.unary_unary_rpc_method_handler( + servicer.UpsertColumns, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UpsertScalars': grpc.unary_unary_rpc_method_handler( + servicer.UpsertScalars, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UpsertMedia': grpc.unary_unary_rpc_method_handler( + servicer.UpsertMedia, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UpsertLogs': grpc.unary_unary_rpc_method_handler( + servicer.UpsertLogs, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UpsertSaves': grpc.unary_unary_rpc_method_handler( + servicer.UpsertSaves, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'DeliverRunFinish': grpc.unary_unary_rpc_method_handler( + servicer.DeliverRunFinish, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.SerializeToString, + ), + 'GetOperationStats': grpc.unary_unary_rpc_method_handler( + servicer.GetOperationStats, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.SerializeToString, + ), + 'ConfirmRunFinish': grpc.unary_unary_rpc_method_handler( + servicer.ConfirmRunFinish, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'swanlab.grpc.core.v2.CoreService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('swanlab.grpc.core.v2.CoreService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class CoreService(object): + """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 + + 相对 v1 的关键变更: + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 + v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 + """ + + @staticmethod + def GetCapabilities(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/GetCapabilities', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def TeardownService(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/TeardownService', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeliverRunStart(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/DeliverRunStart', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpsertColumns(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/UpsertColumns', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpsertScalars(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/UpsertScalars', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpsertMedia(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/UpsertMedia', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpsertLogs(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/UpsertLogs', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpsertSaves(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/UpsertSaves', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeliverRunFinish(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/DeliverRunFinish', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetOperationStats(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/GetOperationStats', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ConfirmRunFinish(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish', + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From b4c42ad25ea3cabb50c4cd2929cb72e4e21942bd Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Fri, 18 Sep 2026 17:16:51 +0800 Subject: [PATCH 02/15] feat: add uds port info --- core/internal/pkg/portinfo/portinfo.go | 271 ++++++++++++++++++++ core/internal/pkg/portinfo/portinfo_test.go | 221 ++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 core/internal/pkg/portinfo/portinfo.go create mode 100644 core/internal/pkg/portinfo/portinfo_test.go diff --git a/core/internal/pkg/portinfo/portinfo.go b/core/internal/pkg/portinfo/portinfo.go new file mode 100644 index 000000000..5d009a256 --- /dev/null +++ b/core/internal/pkg/portinfo/portinfo.go @@ -0,0 +1,271 @@ +// Package portinfo 实现 swanlab-core 端口信息文件(port-file)的严格读写。 +// +// port-file 是 Go core 服务在 listen 成功后向 Python SDK 回报监听端点的约定文件, +// 同时携带 RPC 鉴权所需的 auth token。文件格式(v1)为若干行 key=value 文本, +// 以独立的 EOF 行结尾: +// +// protocol=1 +// unix=/short/private/runtime/core.sock (POSIX 平台) +// sock=12345 (Windows 平台,替代 unix 行) +// auth= +// EOF +// +// 写入通过同目录临时文件 + fsync + chmod(0600) + rename 原子提交,读者只会看到 +// 完整文件;解析对重复 key、未知协议版本、缺失字段、非法端口、非 EOF 结尾和 +// 超长内容一律拒绝。auth token 属于敏感信息,任何错误消息中不得包含其值。 +package portinfo + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" +) + +// 协议与格式约束。 +const ( + // ProtocolVersion 是当前 port-file 格式版本。 + ProtocolVersion = 1 + // MaxFileSize 是 port-file 内容长度上限,超出视为损坏或恶意构造。 + MaxFileSize = 4096 + // authTokenBytes 是 auth token 解码后的字节数(256-bit)。 + authTokenBytes = 32 + // maxUnixPathLen 限定 unix 端点路径长度,实际可用长度还受 sun_path 限制。 + maxUnixPathLen = 256 + // filePerm 是 port-file 的 owner-only 权限。 + filePerm = 0o600 + // eofMarker 是文件结尾标记,必须独占一行。 + eofMarker = "EOF" +) + +// 字段名约定,解析与序列化共用。 +const ( + keyProtocol = "protocol" + keyUnix = "unix" + keySock = "sock" + keyAuth = "auth" +) + +// Info 是 port-file 的结构化内容,UnixPath 与 SockPort 二选一。 +type Info struct { + Protocol int + UnixPath string + SockPort int + AuthToken string +} + +// NewAuthToken 生成 base64url 编码的 256-bit 随机 token, +// 用于写入 port-file 的 auth 字段。 +func NewAuthToken() (string, error) { + buf := make([]byte, authTokenBytes) + if _, err := rand.Read(buf); err != nil { + return "", fmt.Errorf("generate auth token: %w", err) + } + return base64.RawURLEncoding.EncodeToString(buf), nil +} + +// Marshal 将 Info 序列化为 v1 格式字节串,序列化前完成全部校验。 +func Marshal(info *Info) ([]byte, error) { + if err := validate(info); err != nil { + return nil, err + } + var b strings.Builder + fmt.Fprintf(&b, "%s=%d\n", keyProtocol, info.Protocol) + if info.UnixPath != "" { + fmt.Fprintf(&b, "%s=%s\n", keyUnix, info.UnixPath) + } else { + fmt.Fprintf(&b, "%s=%d\n", keySock, info.SockPort) + } + fmt.Fprintf(&b, "%s=%s\n", keyAuth, info.AuthToken) + b.WriteString(eofMarker + "\n") + return []byte(b.String()), nil +} + +// WriteFile 原子写入 port-file:先写同目录临时文件,fsync、chmod 后 rename 覆盖目标。 +// 任一步失败都不会破坏既有目标文件,临时文件会被清理。 +func WriteFile(path string, info *Info) error { + data, err := Marshal(info) + if err != nil { + return err + } + dir := filepath.Dir(path) + tmp, err := os.CreateTemp(dir, ".portinfo-*") + if err != nil { + return fmt.Errorf("create temp port-file in %s: %w", dir, err) + } + tmpName := tmp.Name() + renamed := false + defer func() { + if !renamed { + _ = os.Remove(tmpName) + } + }() + if _, err := tmp.Write(data); err != nil { + _ = tmp.Close() + return fmt.Errorf("write temp port-file: %w", err) + } + // fsync 尽力而为:部分网络文件系统(如 smbfs)不支持 fsync 而返回 EINVAL。 + // port-file 为短命文件且由同机读者在秒级内读取,可见性一致性由 rename 原子性保证, + // fsync 仅作为崩溃防护,失败不阻塞端点回报。 + _ = tmp.Sync() + if err := tmp.Close(); err != nil { + return fmt.Errorf("close temp port-file: %w", err) + } + if err := os.Chmod(tmpName, filePerm); err != nil { + return fmt.Errorf("chmod temp port-file: %w", err) + } + if err := os.Rename(tmpName, path); err != nil { + return fmt.Errorf("commit port-file: %w", err) + } + renamed = true + syncDir(dir) + return nil +} + +// ParseFile 读取并严格解析 port-file。文件超过 MaxFileSize 时按损坏处理。 +func ParseFile(path string) (Info, error) { + f, err := os.Open(path) + if err != nil { + return Info{}, fmt.Errorf("open port-file: %w", err) + } + defer func() { _ = f.Close() }() + data, err := io.ReadAll(io.LimitReader(f, MaxFileSize+1)) + if err != nil { + return Info{}, fmt.Errorf("read port-file: %w", err) + } + return Parse(data) +} + +// Parse 严格解析 v1 格式内容。键值行顺序不限,但每个必要字段恰好出现一次, +// 必须以独立 EOF 行结尾(末尾换行可选)。 +func Parse(data []byte) (Info, error) { + if len(data) > MaxFileSize { + return Info{}, fmt.Errorf("port-file exceeds %d bytes", MaxFileSize) + } + content := strings.TrimSuffix(string(data), "\n") + if !strings.HasSuffix(content, "\n"+eofMarker) && content != eofMarker { + return Info{}, errors.New("port-file must end with an EOF line") + } + body := strings.TrimSuffix(content, "\n"+eofMarker) + if content == eofMarker { + body = "" + } + + var info Info + seen := make(map[string]bool, 3) + for _, line := range strings.Split(body, "\n") { + if line == "" { + return Info{}, errors.New("port-file contains empty line") + } + key, value, ok := strings.Cut(line, "=") + if !ok { + return Info{}, fmt.Errorf("port-file line is not key=value: %q", line) + } + if seen[key] { + return Info{}, fmt.Errorf("port-file contains duplicate key %q", key) + } + switch key { + case keyProtocol: + if value != strconv.Itoa(ProtocolVersion) { + return Info{}, fmt.Errorf("unsupported port-file protocol %q", value) + } + info.Protocol = ProtocolVersion + case keyUnix: + if !strings.HasPrefix(value, "/") || len(value) > maxUnixPathLen { + return Info{}, errors.New("unix endpoint must be an absolute path within length limit") + } + info.UnixPath = value + case keySock: + port, err := parsePort(value) + if err != nil { + return Info{}, err + } + info.SockPort = port + case keyAuth: + if err := validateAuthToken(value); err != nil { + return Info{}, err + } + info.AuthToken = value + default: + return Info{}, fmt.Errorf("port-file contains unknown key %q", key) + } + seen[key] = true + } + + if !seen[keyProtocol] { + return Info{}, fmt.Errorf("port-file missing %q field", keyProtocol) + } + if !seen[keyAuth] { + return Info{}, fmt.Errorf("port-file missing %q field", keyAuth) + } + if seen[keyUnix] == seen[keySock] { + return Info{}, fmt.Errorf("port-file must contain exactly one of %q or %q", keyUnix, keySock) + } + return info, nil +} + +// validate 校验 Info 的全部字段约束。 +func validate(info *Info) error { + if info.Protocol != ProtocolVersion { + return fmt.Errorf("unsupported port-file protocol %d", info.Protocol) + } + hasUnix := info.UnixPath != "" + hasSock := info.SockPort != 0 + if hasUnix == hasSock { + return fmt.Errorf("exactly one of unix path or sock port must be set") + } + if hasUnix { + if !strings.HasPrefix(info.UnixPath, "/") || len(info.UnixPath) > maxUnixPathLen { + return fmt.Errorf("unix socket path must be absolute and at most %d bytes", maxUnixPathLen) + } + } else if info.SockPort < 1 || info.SockPort > 65535 { + return fmt.Errorf("sock port %d out of range", info.SockPort) + } + return validateAuthToken(info.AuthToken) +} + +// validateAuthToken 校验 token 是 base64url 编码且解码后恰好 256-bit。 +// 错误消息不回显 token 值,避免敏感信息泄露。 +func validateAuthToken(token string) error { + raw, err := base64.RawURLEncoding.Strict().DecodeString(token) + if err != nil || len(raw) != authTokenBytes { + return errors.New("auth token must be base64url-encoded 256-bit value") + } + return nil +} + +// parsePort 严格解析十进制端口号:仅数字、无前导零、范围 1-65535。 +func parsePort(value string) (int, error) { + if value == "" || strings.HasPrefix(value, "0") || !isDigits(value) { + return 0, fmt.Errorf("invalid sock port %q", value) + } + port, err := strconv.Atoi(value) + if err != nil || port < 1 || port > 65535 { + return 0, fmt.Errorf("invalid sock port %q", value) + } + return port, nil +} + +func isDigits(s string) bool { + for _, r := range s { + if r < '0' || r > '9' { + return false + } + } + return true +} + +// syncDir 尽力持久化目录项,使 rename 结果落盘;失败不影响写入结果。 +func syncDir(dir string) { + d, err := os.Open(dir) + if err != nil { + return + } + defer func() { _ = d.Close() }() + _ = d.Sync() +} diff --git a/core/internal/pkg/portinfo/portinfo_test.go b/core/internal/pkg/portinfo/portinfo_test.go new file mode 100644 index 000000000..3586a592e --- /dev/null +++ b/core/internal/pkg/portinfo/portinfo_test.go @@ -0,0 +1,221 @@ +package portinfo + +import ( + "bytes" + "encoding/base64" + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +// testToken 返回确定性的合法 256-bit token,避免测试依赖随机数。 +func testToken() string { + return base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{0x42}, authTokenBytes)) +} + +func validUnixContent() string { + return "protocol=1\nunix=/tmp/swanlab/core.sock\nauth=" + testToken() + "\nEOF\n" +} + +func TestMarshalUnixFormat(t *testing.T) { + data, err := Marshal(&Info{Protocol: 1, UnixPath: "/tmp/swanlab/core.sock", AuthToken: testToken()}) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got := string(data); got != validUnixContent() { + t.Fatalf("Marshal output mismatch:\n got: %q\nwant: %q", got, validUnixContent()) + } +} + +func TestMarshalSockFormat(t *testing.T) { + data, err := Marshal(&Info{Protocol: 1, SockPort: 12345, AuthToken: testToken()}) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + want := "protocol=1\nsock=12345\nauth=" + testToken() + "\nEOF\n" + if got := string(data); got != want { + t.Fatalf("Marshal output mismatch:\n got: %q\nwant: %q", got, want) + } +} + +func TestWriteAndParseRoundTrip(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "core.port") + token, err := NewAuthToken() + if err != nil { + t.Fatalf("NewAuthToken: %v", err) + } + want := Info{Protocol: 1, UnixPath: "/tmp/swanlab/core.sock", AuthToken: token} + err = WriteFile(path, &want) + if err != nil { + t.Fatalf("WriteFile: %v", err) + } + got, err := ParseFile(path) + if err != nil { + t.Fatalf("ParseFile: %v", err) + } + if got != want { + t.Fatalf("round trip mismatch: got %+v, want %+v", got, want) + } + if runtime.GOOS != "windows" { + var info os.FileInfo + info, err = os.Stat(path) + if err != nil { + t.Fatalf("Stat: %v", err) + } + if perm := info.Mode().Perm(); perm != filePerm { + t.Fatalf("port-file perm = %o, want %o", perm, filePerm) + } + } + // 原子写入不应遗留临时文件 + var entries []os.DirEntry + entries, err = os.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + if len(entries) != 1 { + t.Fatalf("runtime dir has %d entries after write, want 1", len(entries)) + } +} + +func TestWriteFileAtomicallyReplaces(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "core.port") + first := Info{Protocol: 1, UnixPath: "/tmp/a.sock", AuthToken: testToken()} + if err := WriteFile(path, &first); err != nil { + t.Fatalf("first WriteFile: %v", err) + } + secondToken, err := NewAuthToken() + if err != nil { + t.Fatalf("NewAuthToken: %v", err) + } + second := Info{Protocol: 1, UnixPath: "/tmp/b.sock", AuthToken: secondToken} + err = WriteFile(path, &second) + if err != nil { + t.Fatalf("second WriteFile: %v", err) + } + got, err := ParseFile(path) + if err != nil { + t.Fatalf("ParseFile: %v", err) + } + if got != second { + t.Fatalf("after replace got %+v, want %+v", got, second) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + if len(entries) != 1 { + t.Fatalf("runtime dir has %d entries after replace, want 1", len(entries)) + } +} + +func TestWriteFileRejectsInvalidInfo(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "core.port") + cases := map[string]Info{ + "bad protocol": {Protocol: 2, UnixPath: "/tmp/a.sock", AuthToken: testToken()}, + "both endings": {Protocol: 1, UnixPath: "/tmp/a.sock", SockPort: 80, AuthToken: testToken()}, + "no endpoint": {Protocol: 1, AuthToken: testToken()}, + "relative path": {Protocol: 1, UnixPath: "tmp/a.sock", AuthToken: testToken()}, + "port range": {Protocol: 1, SockPort: 65536, AuthToken: testToken()}, + "bad token": {Protocol: 1, UnixPath: "/tmp/a.sock", AuthToken: "short"}, + } + for name, info := range cases { + if err := WriteFile(path, &info); err == nil { + t.Fatalf("%s: WriteFile unexpectedly succeeded", name) + } + } + if _, err := os.Stat(path); !os.IsNotExist(err) { + t.Fatalf("invalid info must not create target file, stat err = %v", err) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + if len(entries) != 0 { + t.Fatalf("invalid info left %d temp files, want 0", len(entries)) + } +} + +func TestNewAuthToken(t *testing.T) { + first, err := NewAuthToken() + if err != nil { + t.Fatalf("NewAuthToken: %v", err) + } + second, err := NewAuthToken() + if err != nil { + t.Fatalf("NewAuthToken: %v", err) + } + if first == second { + t.Fatal("two generated tokens must differ") + } + if raw, err := base64.RawURLEncoding.Strict().DecodeString(first); err != nil || len(raw) != authTokenBytes { + t.Fatalf("generated token is not 256-bit base64url: decode err = %v", err) + } +} + +func TestParseAcceptsValidVariants(t *testing.T) { + // EOF 行末尾无换行同样合法 + content := strings.TrimSuffix(validUnixContent(), "\n") + if _, err := Parse([]byte(content)); err != nil { + t.Fatalf("parse without trailing newline: %v", err) + } + // 键值行顺序不影响解析 + reordered := "auth=" + testToken() + "\nunix=/tmp/swanlab/core.sock\nprotocol=1\nEOF\n" + if _, err := Parse([]byte(reordered)); err != nil { + t.Fatalf("parse reordered lines: %v", err) + } + // sock 端点(Windows 形态) + sockContent := "protocol=1\nsock=12345\nauth=" + testToken() + "\nEOF\n" + info, err := Parse([]byte(sockContent)) + if err != nil { + t.Fatalf("parse sock content: %v", err) + } + if info.SockPort != 12345 || info.UnixPath != "" { + t.Fatalf("sock parse got %+v", info) + } +} + +func TestParseRejectsMalformed(t *testing.T) { + cases := map[string]string{ + "missing EOF": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\n", + "EOF not own line": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "EOF\n", + "content after EOF": validUnixContent() + "extra\n", + "torn write": "protocol=1\nunix=/tmp/a.sock\n", + "empty body": "EOF\n", + "empty line": "protocol=1\n\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "not key value": "protocol=1\nunix=/tmp/a.sock\nauth\nEOF\n", + "unknown key": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nextra=1\nEOF\n", + "duplicate key": "protocol=1\nprotocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "unknown protocol": "protocol=2\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "non numeric protocol": "protocol=abc\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "missing protocol": "unix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "missing auth": "protocol=1\nunix=/tmp/a.sock\nEOF\n", + "missing endpoint": "protocol=1\nauth=" + testToken() + "\nEOF\n", + "both endpoints": "protocol=1\nunix=/tmp/a.sock\nsock=12345\nauth=" + testToken() + "\nEOF\n", + "relative unix path": "protocol=1\nunix=tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "oversize unix path": "protocol=1\nunix=/" + strings.Repeat("a", maxUnixPathLen) + "\nauth=" + testToken() + "\nEOF\n", + "port zero": "protocol=1\nsock=0\nauth=" + testToken() + "\nEOF\n", + "port range": "protocol=1\nsock=65536\nauth=" + testToken() + "\nEOF\n", + "port leading zero": "protocol=1\nsock=01234\nauth=" + testToken() + "\nEOF\n", + "port not digits": "protocol=1\nsock=12a45\nauth=" + testToken() + "\nEOF\n", + "token too short": "protocol=1\nunix=/tmp/a.sock\nauth=QiQi\nEOF\n", + "token std alphabet": "protocol=1\nunix=/tmp/a.sock\nauth=" + strings.Repeat("+", 43) + "\nEOF\n", + "token padded": "protocol=1\nunix=/tmp/a.sock\nauth=" + strings.TrimSuffix(testToken(), "i") + "i=\nEOF\n", + } + for name, content := range cases { + if _, err := Parse([]byte(content)); err == nil { + t.Fatalf("%s: Parse unexpectedly succeeded", name) + } + } +} + +func TestParseRejectsOversizeContent(t *testing.T) { + content := "protocol=1\nunix=/tmp/" + strings.Repeat("a", MaxFileSize) + "\nauth=" + testToken() + "\nEOF\n" + if _, err := Parse([]byte(content)); err == nil { + t.Fatal("Parse unexpectedly succeeded for oversize content") + } +} From f8871ede5f6451650fd7c07012297bc48d0f399e Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Fri, 18 Sep 2026 17:31:21 +0800 Subject: [PATCH 03/15] feat: add auth token --- .gitignore | 1 + core/cmd/swanlab-core/main.go | 186 ++++++++++++++++++++++------ core/internal/server/controller.go | 57 +++++++++ core/internal/server/server.go | 52 ++++++++ core/internal/server/server_test.go | 121 ++++++++++++++++++ 5 files changed, 378 insertions(+), 39 deletions(-) create mode 100644 core/internal/server/controller.go create mode 100644 core/internal/server/server.go create mode 100644 core/internal/server/server_test.go diff --git a/.gitignore b/.gitignore index 4f2bbf9f5..3d9f3f094 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ CODEBUDDY.md # go core binary swanlab/bin/ +core/bin/ diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index e5bc58adc..a7c2e6661 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -1,16 +1,19 @@ // Command swanlab-core 是 SwanLab Go core 的进程入口。 // -// 当前为发布脚手架阶段:仅建立进程生命周期骨架——版本上报(--version)、 -// 端点监听、父进程退出监控与信号处理;gRPC 服务端(CoreService / -// CoreSyncService / ProbeService)在后续迭代中接入,届时替换 serve 循环。 -// // 端点约定: // -// --listen unix:///path/to/uds Linux/macOS 进程内通信(默认路径由 Python SDK 分配) -// --listen tcp://127.0.0.1:port Windows 回环地址(uds 不可用,named pipe 支持后续提供) +// --listen unix:///path/to/uds Linux/macOS 进程内通信(手动调试入口) +// --listen tcp://127.0.0.1:port Windows 回环地址 +// --port-filename <路径> listen 成功后原子写入端点回报文件(SDK 启动约定) +// +// 未传 --listen 时按平台自选端点:POSIX 使用 port-filename 同目录下的 +// core.sock(UDS,目录需已存在),Windows 使用 127.0.0.1 随机回环端口。 +// --port-filename 与 --owner-token-file 成对出现,owner token 是唯一允许 +// 触发服务级关闭的凭证,通过私有文件传入,不得出现在命令行或日志中。 // -// 生命周期:父进程退出(process 包监控)或收到 SIGINT/SIGTERM 时优雅退出, -// 防止 Python SDK 崩溃后 core 沦为孤儿进程。 +// 生命周期:Teardown RPC、SIGINT/SIGTERM、父进程退出(process 包监控)或 +// Serve 异常统一汇入 service controller 的关闭路径(GracefulStop → 超时 +// 强制 Stop);退出时只清理自己创建的 socket 文件与 port-file。 package main import ( @@ -21,13 +24,19 @@ import ( "net" "os" "os/signal" + "path/filepath" "runtime" "strconv" "strings" "syscall" + "time" + + "google.golang.org/grpc" "github.com/swanhubx/swanlab/core/internal/pkg/console" + "github.com/swanhubx/swanlab/core/internal/pkg/portinfo" "github.com/swanhubx/swanlab/core/internal/pkg/process" + "github.com/swanhubx/swanlab/core/internal/server" ) // version 与 commit 由构建管线通过 -ldflags -X 注入(见 core/hatch.py), @@ -49,6 +58,14 @@ const ( exitRunError = 1 ) +// 自选端点与收尾参数。 +const ( + coreSocketName = "core.sock" + loopbackAddr = "127.0.0.1:0" + shutdownGrace = 10 * time.Second + secretFileMaxSize = 4096 +) + func main() { os.Exit(run(os.Args[1:])) } @@ -57,7 +74,11 @@ func run(args []string) int { fs := flag.NewFlagSet("swanlab-core", flag.ContinueOnError) printVersion := fs.Bool("version", false, "打印版本信息后退出") listenAddr := fs.String("listen", os.Getenv(envListenAddr), - "监听端点,格式 unix:// 或 tcp://<地址:端口>;Windows 仅支持 tcp:// 回环地址") + "监听端点,格式 unix:// 或 tcp://<地址:端口>;未指定时按平台自选端点") + portFilename := fs.String("port-filename", "", + "端点回报文件路径;listen 成功后原子写入,供调用方轮询获取端点与鉴权 token") + ownerTokenFile := fs.String("owner-token-file", "", + "owner token 文件路径,仅服务所有者持有,是触发服务级关闭的唯一凭证") parentPID := fs.Int("parent-pid", envInt(envParentPID), "预期父进程 PID,父进程退出时 core 随之退出;未指定时取启动瞬间的实际父进程") if err := fs.Parse(args); err != nil { @@ -71,20 +92,44 @@ func run(args []string) int { fmt.Printf("swanlab-core %s (commit %s)\n", version, commit) return 0 } - if *listenAddr == "" { - console.Error("未指定监听端点:通过 --listen 或环境变量 " + envListenAddr + " 传入") + if *listenAddr == "" && *portFilename == "" { + console.Error("未指定监听端点:通过 --listen(手动调试)或 --port-filename(启动约定)传入") return exitUsageError } + if (*portFilename != "") != (*ownerTokenFile != "") { + console.Error("--port-filename 与 --owner-token-file 必须成对提供") + return exitUsageError + } + + // 自建资源记录,退出时只清理自己创建的部分。 + var socketPath string + wrotePortFile := false + defer func() { + cleanupSocket(socketPath) + if wrotePortFile { + _ = os.Remove(*portFilename) + } + }() - ln, err := listen(*listenAddr) + // owner token 先于任何资源创建读取,尽早失败。 + ownerToken, err := readOwnerToken(*ownerTokenFile) + if err != nil { + console.Error("读取 owner token 失败:", err) + return exitRunError + } + + ln, err := openEndpoint(*listenAddr, *portFilename) if err != nil { console.Error("监听失败:", err) return exitRunError } - defer ln.Close() + defer func() { _ = ln.Close() }() + if addr, ok := ln.Addr().(*net.UnixAddr); ok && !strings.HasPrefix(addr.Name, "@") { + socketPath = addr.Name + } - // 父进程监控:显式传入的 PID 优先(Python SDK 启动约定),未传时回退为 - // 监控启动瞬间的实际父进程(本地终端运行场景)。监控建立失败按约定终止启动。 + // 父进程监控:显式传入的 PID 优先(启动约定),未传时回退为监控启动 + // 瞬间的实际父进程(本地终端运行场景)。监控建立失败按约定终止启动。 pid := *parentPID if pid <= 0 { pid = os.Getppid() @@ -95,36 +140,85 @@ func run(args []string) int { return exitRunError } + grpcServer := grpc.NewServer() + ctrl := server.NewController(grpcServer, shutdownGrace) + server.NewService(ownerToken, ctrl).Register(grpcServer) + + // listen 与 server 初始化均成功后才写 port-file。 + if *portFilename != "" { + authToken, err2 := portinfo.NewAuthToken() + if err2 != nil { + console.Error("生成 auth token 失败:", err2) + return exitRunError + } + info := portinfo.Info{Protocol: portinfo.ProtocolVersion, AuthToken: authToken} + switch addr := ln.Addr().(type) { + case *net.UnixAddr: + info.UnixPath = addr.Name + case *net.TCPAddr: + info.SockPort = addr.Port + default: + console.Error("无法识别的监听端点类型:", ln.Addr()) + return exitRunError + } + if err2 = portinfo.WriteFile(*portFilename, &info); err2 != nil { + console.Error("写入 port-file 失败:", err2) + return exitRunError + } + wrotePortFile = true + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() - console.Infof("swanlab-core %s listening on %s (parent pid %d)", version, *listenAddr, pid) + console.Infof("swanlab-core %s listening on %s (parent pid %d)", version, ln.Addr(), pid) serveErr := make(chan error, 1) go func() { - serveErr <- serve(ln) + serveErr <- grpcServer.Serve(ln) }() + exitCode := 0 + var cause string + var serveFailure error select { case <-ctx.Done(): console.Info("收到退出信号,正在关闭") + cause = "signal" case <-parentExited: console.Warning("父进程已退出,core 随之退出") + cause = "parent-exit" case err := <-serveErr: - if err != nil { - console.Error("监听异常退出:", err) - return exitRunError - } + serveFailure = err + cause = "serve-error" + } + ctrl.Shutdown(cause) + // 等待 Serve 返回与关闭序列完成(两者任一先行均可)。 + select { + case serveFailure = <-serveErr: + case <-ctrl.Done(): } - return 0 + <-ctrl.Done() + if serveFailure != nil { + console.Error("gRPC Serve 异常退出:", serveFailure) + exitCode = exitRunError + } + return exitCode } -// listen 按协议前缀创建监听器。uds 仅在非 Windows 平台可用;Windows 使用 -// TCP 回环地址兜底(named pipe 接入后在此分支扩展)。 -func listen(addr string) (net.Listener, error) { - scheme, rest, ok := strings.Cut(addr, "://") +// openEndpoint 创建监听器。显式 --listen 优先(手动调试);否则按平台自选: +// POSIX 使用 port-filename 同目录下的 UDS,Windows 使用随机回环端口。 +func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { + if listenAddr == "" { + if runtime.GOOS == "windows" { + return net.Listen("tcp", loopbackAddr) + } + sockPath := filepath.Join(filepath.Dir(portFilename), coreSocketName) + return net.Listen("unix", sockPath) + } + scheme, rest, ok := strings.Cut(listenAddr, "://") if !ok { - return nil, fmt.Errorf("监听端点缺少协议前缀(unix:// 或 tcp://): %s", addr) + return nil, fmt.Errorf("监听端点缺少协议前缀(unix:// 或 tcp://): %s", listenAddr) } switch scheme { case "unix": @@ -139,19 +233,33 @@ func listen(addr string) (net.Listener, error) { } } -// serve 接受连接后立即关闭。脚手架阶段仅验证端点连通性, -// gRPC 服务端就绪后由此接入 serve 逻辑。 -func serve(ln net.Listener) error { - for { - conn, err := ln.Accept() - if err != nil { - if errors.Is(err, net.ErrClosed) { - return nil - } - return err - } - _ = conn.Close() +// readOwnerToken 读取 owner token 文件;路径为空返回空串。内容去除首尾空白, +// 拒绝空文件与超长文件,token 值不进入日志。 +func readOwnerToken(path string) (string, error) { + if path == "" { + return "", nil + } + data, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("read %s: %w", path, err) + } + if len(data) > secretFileMaxSize { + return "", fmt.Errorf("owner token file exceeds %d bytes: %s", secretFileMaxSize, path) + } + token := strings.TrimSpace(string(data)) + if token == "" { + return "", fmt.Errorf("owner token file is empty: %s", path) + } + return token, nil +} + +// cleanupSocket 删除自己创建的 UDS socket 文件;Linux 抽象 socket(@ 前缀) +// 不占文件系统,无需清理。 +func cleanupSocket(path string) { + if path == "" || strings.HasPrefix(path, "@") { + return } + _ = os.Remove(path) } // envInt 解析整型环境变量,缺失或非法时返回 0。 diff --git a/core/internal/server/controller.go b/core/internal/server/controller.go new file mode 100644 index 000000000..172cd7205 --- /dev/null +++ b/core/internal/server/controller.go @@ -0,0 +1,57 @@ +package server + +import ( + "sync" + "time" + + "google.golang.org/grpc" + + "github.com/swanhubx/swanlab/core/internal/pkg/console" +) + +// Controller 仲裁服务的统一关闭路径。 +// +// Teardown RPC、SIGINT/SIGTERM、父进程退出通知与 Serve 异常都汇入同一条 +// 收尾序列:先 GracefulStop 等待在途请求完成,超过 grace 时限后强制 Stop, +// 并保证 Serve 一定返回。Shutdown 幂等,多次触发只执行一次。 +type Controller struct { + server *grpc.Server + grace time.Duration + once sync.Once + done chan struct{} +} + +// NewController 包装一个 gRPC Server,grace 为优雅关闭的等待上限。 +func NewController(g *grpc.Server, grace time.Duration) *Controller { + return &Controller{ + server: g, + grace: grace, + done: make(chan struct{}), + } +} + +// Shutdown 幂等触发关闭;cause 仅用于日志,标识关闭来源。 +func (c *Controller) Shutdown(cause string) { + c.once.Do(func() { + console.Infof("core 服务开始关闭(%s)", cause) + go func() { + defer close(c.done) + graceful := make(chan struct{}) + go func() { + c.server.GracefulStop() + close(graceful) + }() + select { + case <-graceful: + case <-time.After(c.grace): + c.server.Stop() + <-graceful + } + }() + }) +} + +// Done 在关闭序列完成后关闭,供调用方等待收尾结束。 +func (c *Controller) Done() <-chan struct{} { + return c.done +} diff --git a/core/internal/server/server.go b/core/internal/server/server.go new file mode 100644 index 000000000..97016f6b8 --- /dev/null +++ b/core/internal/server/server.go @@ -0,0 +1,52 @@ +// Package server 提供 swanlab-core 的 gRPC 服务端实现。 +// +// 当前为脚手架阶段:CoreService v2 已注册并可完成服务级关闭, +// 鉴权 interceptor、capability 应答与 run 会话路由在后续迭代接入。 +package server + +import ( + "context" + "crypto/hmac" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + corev2 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2" +) + +// Service 实现 CoreService v2。 +// +// 除 TeardownService 外的所有 RPC 暂由嵌入的 Unimplemented 实现接管, +// 返回 UNIMPLEMENTED;run 级接口在会话路由迭代中逐步补齐。 +type Service struct { + corev2.UnimplementedCoreServiceServer + + ownerToken string + controller *Controller +} + +// NewService 创建服务实例。ownerToken 为 spawn owner 通过私有文件传入的 +// 服务级令牌,是唯一允许触发 TeardownService 的凭证。 +func NewService(ownerToken string, controller *Controller) *Service { + return &Service{ + ownerToken: ownerToken, + controller: controller, + } +} + +// Register 将服务注册到 gRPC Server。 +func (s *Service) Register(g *grpc.Server) { + corev2.RegisterCoreServiceServer(g, s) +} + +// TeardownService 关闭整个服务进程,仅接受正确的 owner token。 +// 校验使用常量时间比较;token 值不得出现在日志或错误消息中。 +func (s *Service) TeardownService(_ context.Context, req *corev2.TeardownServiceRequest) (*corev2.TeardownServiceResponse, error) { + if s.ownerToken == "" || !hmac.Equal([]byte(req.OwnerToken), []byte(s.ownerToken)) { + return nil, status.Error(codes.PermissionDenied, "invalid owner token") + } + // 异步触发统一关闭路径,保证本响应先于连接关闭送达调用方。 + s.controller.Shutdown("teardown") + return &corev2.TeardownServiceResponse{}, nil +} diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go new file mode 100644 index 000000000..861c47c95 --- /dev/null +++ b/core/internal/server/server_test.go @@ -0,0 +1,121 @@ +package server + +import ( + "context" + "net" + "testing" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + + corev2 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2" +) + +const ( + bufconnSize = 1 << 20 + testGrace = 2 * time.Second + noShutdownWindow = 200 * time.Millisecond + callTimeout = 2 * time.Second +) + +type testEnv struct { + client corev2.CoreServiceClient + ctrl *Controller +} + +// newTestEnv 在内存连接上启动完整服务端,返回客户端句柄与关闭控制器。 +func newTestEnv(t *testing.T, ownerToken string) *testEnv { + t.Helper() + g := grpc.NewServer() + ctrl := NewController(g, testGrace) + NewService(ownerToken, ctrl).Register(g) + lis := bufconn.Listen(bufconnSize) + go func() { _ = g.Serve(lis) }() + conn, err := grpc.NewClient("passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return lis.DialContext(context.Background()) + }), + ) + if err != nil { + t.Fatalf("grpc.NewClient: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + ctrl.Shutdown("test-cleanup") + <-ctrl.Done() + }) + return &testEnv{client: corev2.NewCoreServiceClient(conn), ctrl: ctrl} +} + +func TestTeardownServiceRejectsWrongToken(t *testing.T) { + env := newTestEnv(t, "owner-secret") + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + defer cancel() + _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: "wrong-token"}) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("TeardownService err = %v, want PermissionDenied", err) + } + select { + case <-env.ctrl.Done(): + t.Fatal("wrong owner token must not trigger shutdown") + case <-time.After(noShutdownWindow): + } +} + +func TestTeardownServiceShutsDownServer(t *testing.T) { + env := newTestEnv(t, "owner-secret") + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + defer cancel() + if _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: "owner-secret"}); err != nil { + t.Fatalf("TeardownService: %v", err) + } + select { + case <-env.ctrl.Done(): + case <-time.After(callTimeout): + t.Fatal("shutdown not completed after valid teardown") + } +} + +func TestTeardownServiceRejectsEmptyConfiguredToken(t *testing.T) { + env := newTestEnv(t, "") + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + defer cancel() + _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: ""}) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("TeardownService err = %v, want PermissionDenied", err) + } +} + +func TestRunLevelRPCsUnimplemented(t *testing.T) { + env := newTestEnv(t, "owner-secret") + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + defer cancel() + if _, err := env.client.GetCapabilities(ctx, &corev2.GetCapabilitiesRequest{}); status.Code(err) != codes.Unimplemented { + t.Fatalf("GetCapabilities err = %v, want Unimplemented", err) + } + if _, err := env.client.UpsertScalars(ctx, &corev2.UpsertScalarsRequest{}); status.Code(err) != codes.Unimplemented { + t.Fatalf("UpsertScalars err = %v, want Unimplemented", err) + } + if _, err := env.client.DeliverRunStart(ctx, &corev2.DeliverRunStartRequest{}); status.Code(err) != codes.Unimplemented { + t.Fatalf("DeliverRunStart err = %v, want Unimplemented", err) + } +} + +func TestControllerShutdownIdempotent(t *testing.T) { + g := grpc.NewServer() + ctrl := NewController(g, testGrace) + ctrl.Shutdown("first") + ctrl.Shutdown("second") + select { + case <-ctrl.Done(): + case <-time.After(callTimeout): + t.Fatal("controller Done not closed after Shutdown") + } + // 重复读取已关闭的 Done 不应阻塞或 panic + <-ctrl.Done() +} From 8c3806851eb0d688d9bf1d2c925ce7f8f9c472c4 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Fri, 18 Sep 2026 17:38:49 +0800 Subject: [PATCH 04/15] chore: update log format --- core/cmd/swanlab-core/main.go | 38 +++++++++++++++--------------- core/internal/server/controller.go | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index a7c2e6661..0c4911b12 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -72,15 +72,15 @@ func main() { func run(args []string) int { fs := flag.NewFlagSet("swanlab-core", flag.ContinueOnError) - printVersion := fs.Bool("version", false, "打印版本信息后退出") + printVersion := fs.Bool("version", false, "print version and exit") listenAddr := fs.String("listen", os.Getenv(envListenAddr), - "监听端点,格式 unix:// 或 tcp://<地址:端口>;未指定时按平台自选端点") + "listen endpoint, unix:// or tcp://; auto-selected per platform when unset") portFilename := fs.String("port-filename", "", - "端点回报文件路径;listen 成功后原子写入,供调用方轮询获取端点与鉴权 token") + "endpoint report file; atomically written once listen succeeds, for callers to poll") ownerTokenFile := fs.String("owner-token-file", "", - "owner token 文件路径,仅服务所有者持有,是触发服务级关闭的唯一凭证") + "owner token file, held only by the service owner; sole credential for service-level teardown") parentPID := fs.Int("parent-pid", envInt(envParentPID), - "预期父进程 PID,父进程退出时 core 随之退出;未指定时取启动瞬间的实际父进程") + "expected parent PID; core exits when the parent exits, defaults to the actual parent at startup") if err := fs.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { return 0 @@ -93,11 +93,11 @@ func run(args []string) int { return 0 } if *listenAddr == "" && *portFilename == "" { - console.Error("未指定监听端点:通过 --listen(手动调试)或 --port-filename(启动约定)传入") + console.Error("no listen endpoint: pass --listen (manual debug) or --port-filename (SDK startup convention)") return exitUsageError } if (*portFilename != "") != (*ownerTokenFile != "") { - console.Error("--port-filename 与 --owner-token-file 必须成对提供") + console.Error("--port-filename and --owner-token-file must be provided together") return exitUsageError } @@ -114,13 +114,13 @@ func run(args []string) int { // owner token 先于任何资源创建读取,尽早失败。 ownerToken, err := readOwnerToken(*ownerTokenFile) if err != nil { - console.Error("读取 owner token 失败:", err) + console.Error("failed to read owner token:", err) return exitRunError } ln, err := openEndpoint(*listenAddr, *portFilename) if err != nil { - console.Error("监听失败:", err) + console.Error("listen failed:", err) return exitRunError } defer func() { _ = ln.Close() }() @@ -136,7 +136,7 @@ func run(args []string) int { } parentExited, err := process.NotifyOnParentExit(pid) if err != nil { - console.Error("父进程监控建立失败,终止启动:", err) + console.Error("failed to watch parent process, aborting startup:", err) return exitRunError } @@ -148,7 +148,7 @@ func run(args []string) int { if *portFilename != "" { authToken, err2 := portinfo.NewAuthToken() if err2 != nil { - console.Error("生成 auth token 失败:", err2) + console.Error("failed to generate auth token:", err2) return exitRunError } info := portinfo.Info{Protocol: portinfo.ProtocolVersion, AuthToken: authToken} @@ -158,11 +158,11 @@ func run(args []string) int { case *net.TCPAddr: info.SockPort = addr.Port default: - console.Error("无法识别的监听端点类型:", ln.Addr()) + console.Error("unrecognized listener address type:", ln.Addr()) return exitRunError } if err2 = portinfo.WriteFile(*portFilename, &info); err2 != nil { - console.Error("写入 port-file 失败:", err2) + console.Error("failed to write port-file:", err2) return exitRunError } wrotePortFile = true @@ -183,10 +183,10 @@ func run(args []string) int { var serveFailure error select { case <-ctx.Done(): - console.Info("收到退出信号,正在关闭") + console.Info("shutdown signal received, stopping") cause = "signal" case <-parentExited: - console.Warning("父进程已退出,core 随之退出") + console.Warning("parent process exited, stopping core") cause = "parent-exit" case err := <-serveErr: serveFailure = err @@ -200,7 +200,7 @@ func run(args []string) int { } <-ctrl.Done() if serveFailure != nil { - console.Error("gRPC Serve 异常退出:", serveFailure) + console.Error("gRPC Serve exited with error:", serveFailure) exitCode = exitRunError } return exitCode @@ -218,18 +218,18 @@ func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { } scheme, rest, ok := strings.Cut(listenAddr, "://") if !ok { - return nil, fmt.Errorf("监听端点缺少协议前缀(unix:// 或 tcp://): %s", listenAddr) + return nil, fmt.Errorf("listen endpoint missing scheme prefix (unix:// or tcp://): %s", listenAddr) } switch scheme { case "unix": if runtime.GOOS == "windows" { - return nil, errors.New("windows 平台不支持 unix:// 端点,请使用 tcp://127.0.0.1:<端口>") + return nil, errors.New("unix:// endpoints are not supported on Windows; use tcp://127.0.0.1:") } return net.Listen("unix", rest) case "tcp": return net.Listen("tcp", rest) default: - return nil, fmt.Errorf("不支持的监听协议 %q(仅 unix:// 或 tcp://)", scheme) + return nil, fmt.Errorf("unsupported listen scheme %q (only unix:// or tcp://)", scheme) } } diff --git a/core/internal/server/controller.go b/core/internal/server/controller.go index 172cd7205..73eaa4211 100644 --- a/core/internal/server/controller.go +++ b/core/internal/server/controller.go @@ -33,7 +33,7 @@ func NewController(g *grpc.Server, grace time.Duration) *Controller { // Shutdown 幂等触发关闭;cause 仅用于日志,标识关闭来源。 func (c *Controller) Shutdown(cause string) { c.once.Do(func() { - console.Infof("core 服务开始关闭(%s)", cause) + console.Infof("core service shutting down (%s)", cause) go func() { defer close(c.done) graceful := make(chan struct{}) From 81e9accfbb4ffba104928f79795447c940fe024c Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Sun, 20 Sep 2026 14:31:02 +0800 Subject: [PATCH 05/15] chore: simplify proto --- core/internal/server/server.go | 14 +- core/internal/server/server_test.go | 18 +- core/proto/swanlab/grpc/core/v1/core.pb.go | 634 +++++++-- .../swanlab/grpc/core/v1/core_grpc.pb.go | 132 +- core/proto/swanlab/grpc/core/v2/core.pb.go | 1191 ----------------- .../swanlab/grpc/core/v2/core_grpc.pb.go | 546 -------- protos/swanlab/grpc/core/v1/core.proto | 84 +- protos/swanlab/grpc/core/v2/core.proto | 170 --- .../proto/swanlab/grpc/core/v1/core_pb2.py | 62 +- .../proto/swanlab/grpc/core/v1/core_pb2.pyi | 90 +- .../swanlab/grpc/core/v1/core_pb2_grpc.py | 132 +- .../proto/swanlab/grpc/core/v2/__init__.py | 0 .../proto/swanlab/grpc/core/v2/core_pb2.py | 79 -- .../proto/swanlab/grpc/core/v2/core_pb2.pyi | 167 --- .../swanlab/grpc/core/v2/core_pb2_grpc.py | 567 -------- 15 files changed, 963 insertions(+), 2923 deletions(-) delete mode 100644 core/proto/swanlab/grpc/core/v2/core.pb.go delete mode 100644 core/proto/swanlab/grpc/core/v2/core_grpc.pb.go delete mode 100644 protos/swanlab/grpc/core/v2/core.proto delete mode 100644 swanlab/proto/swanlab/grpc/core/v2/__init__.py delete mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2.py delete mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi delete mode 100644 swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 97016f6b8..6b3dff07a 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -1,6 +1,6 @@ // Package server 提供 swanlab-core 的 gRPC 服务端实现。 // -// 当前为脚手架阶段:CoreService v2 已注册并可完成服务级关闭, +// 当前为脚手架阶段:CoreService 已注册并可完成服务级关闭, // 鉴权 interceptor、capability 应答与 run 会话路由在后续迭代接入。 package server @@ -12,15 +12,15 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - corev2 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2" + corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" ) -// Service 实现 CoreService v2。 +// Service 实现 CoreService。 // // 除 TeardownService 外的所有 RPC 暂由嵌入的 Unimplemented 实现接管, // 返回 UNIMPLEMENTED;run 级接口在会话路由迭代中逐步补齐。 type Service struct { - corev2.UnimplementedCoreServiceServer + corev1.UnimplementedCoreServiceServer ownerToken string controller *Controller @@ -37,16 +37,16 @@ func NewService(ownerToken string, controller *Controller) *Service { // Register 将服务注册到 gRPC Server。 func (s *Service) Register(g *grpc.Server) { - corev2.RegisterCoreServiceServer(g, s) + corev1.RegisterCoreServiceServer(g, s) } // TeardownService 关闭整个服务进程,仅接受正确的 owner token。 // 校验使用常量时间比较;token 值不得出现在日志或错误消息中。 -func (s *Service) TeardownService(_ context.Context, req *corev2.TeardownServiceRequest) (*corev2.TeardownServiceResponse, error) { +func (s *Service) TeardownService(_ context.Context, req *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { if s.ownerToken == "" || !hmac.Equal([]byte(req.OwnerToken), []byte(s.ownerToken)) { return nil, status.Error(codes.PermissionDenied, "invalid owner token") } // 异步触发统一关闭路径,保证本响应先于连接关闭送达调用方。 s.controller.Shutdown("teardown") - return &corev2.TeardownServiceResponse{}, nil + return &corev1.TeardownServiceResponse{}, nil } diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index 861c47c95..c9ea77b8d 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -12,7 +12,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" - corev2 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2" + corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" ) const ( @@ -23,7 +23,7 @@ const ( ) type testEnv struct { - client corev2.CoreServiceClient + client corev1.CoreServiceClient ctrl *Controller } @@ -49,14 +49,14 @@ func newTestEnv(t *testing.T, ownerToken string) *testEnv { ctrl.Shutdown("test-cleanup") <-ctrl.Done() }) - return &testEnv{client: corev2.NewCoreServiceClient(conn), ctrl: ctrl} + return &testEnv{client: corev1.NewCoreServiceClient(conn), ctrl: ctrl} } func TestTeardownServiceRejectsWrongToken(t *testing.T) { env := newTestEnv(t, "owner-secret") ctx, cancel := context.WithTimeout(context.Background(), callTimeout) defer cancel() - _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: "wrong-token"}) + _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: "wrong-token"}) if status.Code(err) != codes.PermissionDenied { t.Fatalf("TeardownService err = %v, want PermissionDenied", err) } @@ -71,7 +71,7 @@ func TestTeardownServiceShutsDownServer(t *testing.T) { env := newTestEnv(t, "owner-secret") ctx, cancel := context.WithTimeout(context.Background(), callTimeout) defer cancel() - if _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: "owner-secret"}); err != nil { + if _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: "owner-secret"}); err != nil { t.Fatalf("TeardownService: %v", err) } select { @@ -85,7 +85,7 @@ func TestTeardownServiceRejectsEmptyConfiguredToken(t *testing.T) { env := newTestEnv(t, "") ctx, cancel := context.WithTimeout(context.Background(), callTimeout) defer cancel() - _, err := env.client.TeardownService(ctx, &corev2.TeardownServiceRequest{OwnerToken: ""}) + _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: ""}) if status.Code(err) != codes.PermissionDenied { t.Fatalf("TeardownService err = %v, want PermissionDenied", err) } @@ -95,13 +95,13 @@ func TestRunLevelRPCsUnimplemented(t *testing.T) { env := newTestEnv(t, "owner-secret") ctx, cancel := context.WithTimeout(context.Background(), callTimeout) defer cancel() - if _, err := env.client.GetCapabilities(ctx, &corev2.GetCapabilitiesRequest{}); status.Code(err) != codes.Unimplemented { + if _, err := env.client.GetCapabilities(ctx, &corev1.GetCapabilitiesRequest{}); status.Code(err) != codes.Unimplemented { t.Fatalf("GetCapabilities err = %v, want Unimplemented", err) } - if _, err := env.client.UpsertScalars(ctx, &corev2.UpsertScalarsRequest{}); status.Code(err) != codes.Unimplemented { + if _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{}); status.Code(err) != codes.Unimplemented { t.Fatalf("UpsertScalars err = %v, want Unimplemented", err) } - if _, err := env.client.DeliverRunStart(ctx, &corev2.DeliverRunStartRequest{}); status.Code(err) != codes.Unimplemented { + if _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{}); status.Code(err) != codes.Unimplemented { t.Fatalf("DeliverRunStart err = %v, want Unimplemented", err) } } diff --git a/core/proto/swanlab/grpc/core/v1/core.pb.go b/core/proto/swanlab/grpc/core/v1/core.pb.go index 46d2a3dde..9afd7546f 100644 --- a/core/proto/swanlab/grpc/core/v1/core.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core.pb.go @@ -29,6 +29,235 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// GetCapabilities 请求。当前无字段,预留后续扩展。 +type GetCapabilitiesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCapabilitiesRequest) Reset() { + *x = GetCapabilitiesRequest{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCapabilitiesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCapabilitiesRequest) ProtoMessage() {} + +func (x *GetCapabilitiesRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCapabilitiesRequest.ProtoReflect.Descriptor instead. +func (*GetCapabilitiesRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{0} +} + +// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, +// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 +type GetCapabilitiesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProtocolVersion string `protobuf:"bytes,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` // 协议版本,与 Python SDK 期望版本锁步 + CoreVersion string `protobuf:"bytes,2,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` // core 二进制编译版本(构建管线注入) + Lifecycle bool `protobuf:"varint,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"` // 生命周期与进程管理能力 + SupportedModes []string `protobuf:"bytes,4,rep,name=supported_modes,json=supportedModes,proto3" json:"supported_modes,omitempty"` // 支持的 run 模式,如 online/local/offline + Store bool `protobuf:"varint,5,opt,name=store,proto3" json:"store,omitempty"` // 本地持久化能力 + Transport bool `protobuf:"varint,6,opt,name=transport,proto3" json:"transport,omitempty"` // 云端上传能力 + Payload bool `protobuf:"varint,7,opt,name=payload,proto3" json:"payload,omitempty"` // 内联 payload 能力(online skip_store 模式) + Sync bool `protobuf:"varint,8,opt,name=sync,proto3" json:"sync,omitempty"` // 离线数据同步能力 + MaxReceiveMessageBytes int64 `protobuf:"varint,9,opt,name=max_receive_message_bytes,json=maxReceiveMessageBytes,proto3" json:"max_receive_message_bytes,omitempty"` // 服务端单条消息接收上限,用于客户端协商 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCapabilitiesResponse) Reset() { + *x = GetCapabilitiesResponse{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCapabilitiesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCapabilitiesResponse) ProtoMessage() {} + +func (x *GetCapabilitiesResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCapabilitiesResponse.ProtoReflect.Descriptor instead. +func (*GetCapabilitiesResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCapabilitiesResponse) GetProtocolVersion() string { + if x != nil { + return x.ProtocolVersion + } + return "" +} + +func (x *GetCapabilitiesResponse) GetCoreVersion() string { + if x != nil { + return x.CoreVersion + } + return "" +} + +func (x *GetCapabilitiesResponse) GetLifecycle() bool { + if x != nil { + return x.Lifecycle + } + return false +} + +func (x *GetCapabilitiesResponse) GetSupportedModes() []string { + if x != nil { + return x.SupportedModes + } + return nil +} + +func (x *GetCapabilitiesResponse) GetStore() bool { + if x != nil { + return x.Store + } + return false +} + +func (x *GetCapabilitiesResponse) GetTransport() bool { + if x != nil { + return x.Transport + } + return false +} + +func (x *GetCapabilitiesResponse) GetPayload() bool { + if x != nil { + return x.Payload + } + return false +} + +func (x *GetCapabilitiesResponse) GetSync() bool { + if x != nil { + return x.Sync + } + return false +} + +func (x *GetCapabilitiesResponse) GetMaxReceiveMessageBytes() int64 { + if x != nil { + return x.MaxReceiveMessageBytes + } + return 0 +} + +// TeardownService 请求。 +type TeardownServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TeardownServiceRequest) Reset() { + *x = TeardownServiceRequest{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TeardownServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownServiceRequest) ProtoMessage() {} + +func (x *TeardownServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownServiceRequest.ProtoReflect.Descriptor instead. +func (*TeardownServiceRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{2} +} + +func (x *TeardownServiceRequest) GetOwnerToken() string { + if x != nil { + return x.OwnerToken + } + return "" +} + +// TeardownService 响应。 +type TeardownServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TeardownServiceResponse) Reset() { + *x = TeardownServiceResponse{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TeardownServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownServiceResponse) ProtoMessage() {} + +func (x *TeardownServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownServiceResponse.ProtoReflect.Descriptor instead. +func (*TeardownServiceResponse) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{3} +} + // DeliverRunStart 请求,生命周期函数,运行正式开始 type DeliverRunStartRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -40,7 +269,7 @@ type DeliverRunStartRequest struct { func (x *DeliverRunStartRequest) Reset() { *x = DeliverRunStartRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -52,7 +281,7 @@ func (x *DeliverRunStartRequest) String() string { func (*DeliverRunStartRequest) ProtoMessage() {} func (x *DeliverRunStartRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -65,7 +294,7 @@ func (x *DeliverRunStartRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeliverRunStartRequest.ProtoReflect.Descriptor instead. func (*DeliverRunStartRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{0} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{4} } func (x *DeliverRunStartRequest) GetCoreSettings() *v1.CoreSettings { @@ -93,13 +322,14 @@ type DeliverRunStartResponse struct { GlobalStep int64 `protobuf:"varint,6,opt,name=global_step,json=globalStep,proto3" json:"global_step,omitempty"` // 起始全局步数 GlobalSystemStep int64 `protobuf:"varint,7,opt,name=global_system_step,json=globalSystemStep,proto3" json:"global_system_step,omitempty"` // 起始全局系统步数 NewExperiment bool `protobuf:"varint,8,opt,name=new_experiment,json=newExperiment,proto3" json:"new_experiment,omitempty"` // 是否是新实验 + RunHandle string `protobuf:"bytes,9,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeliverRunStartResponse) Reset() { *x = DeliverRunStartResponse{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -111,7 +341,7 @@ func (x *DeliverRunStartResponse) String() string { func (*DeliverRunStartResponse) ProtoMessage() {} func (x *DeliverRunStartResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -124,7 +354,7 @@ func (x *DeliverRunStartResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeliverRunStartResponse.ProtoReflect.Descriptor instead. func (*DeliverRunStartResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{1} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{5} } func (x *DeliverRunStartResponse) GetSuccess() bool { @@ -183,17 +413,25 @@ func (x *DeliverRunStartResponse) GetNewExperiment() bool { return false } +func (x *DeliverRunStartResponse) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // UpsertColumns 请求,异步插入一批列定义 type UpsertColumnsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Columns []*v12.ColumnRecord `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` // 列定义列表 + Columns []*v12.ColumnRecord `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` // 列定义列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpsertColumnsRequest) Reset() { *x = UpsertColumnsRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[2] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -205,7 +443,7 @@ func (x *UpsertColumnsRequest) String() string { func (*UpsertColumnsRequest) ProtoMessage() {} func (x *UpsertColumnsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[2] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -218,7 +456,7 @@ func (x *UpsertColumnsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertColumnsRequest.ProtoReflect.Descriptor instead. func (*UpsertColumnsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{2} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{6} } func (x *UpsertColumnsRequest) GetColumns() []*v12.ColumnRecord { @@ -228,17 +466,25 @@ func (x *UpsertColumnsRequest) GetColumns() []*v12.ColumnRecord { return nil } +func (x *UpsertColumnsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // UpsertScalars 请求,异步插入一批标量数据 type UpsertScalarsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Data []*v13.ScalarRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 标量记录列表 + Data []*v13.ScalarRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 标量记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpsertScalarsRequest) Reset() { *x = UpsertScalarsRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[3] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -250,7 +496,7 @@ func (x *UpsertScalarsRequest) String() string { func (*UpsertScalarsRequest) ProtoMessage() {} func (x *UpsertScalarsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[3] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -263,7 +509,7 @@ func (x *UpsertScalarsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertScalarsRequest.ProtoReflect.Descriptor instead. func (*UpsertScalarsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{3} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{7} } func (x *UpsertScalarsRequest) GetData() []*v13.ScalarRecord { @@ -273,17 +519,25 @@ func (x *UpsertScalarsRequest) GetData() []*v13.ScalarRecord { return nil } +func (x *UpsertScalarsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // UpsertMedia 请求,异步插入一批媒体数据 type UpsertMediaRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Data []*v13.MediaRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 媒体记录列表 + Data []*v13.MediaRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 媒体记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpsertMediaRequest) Reset() { *x = UpsertMediaRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[4] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -295,7 +549,7 @@ func (x *UpsertMediaRequest) String() string { func (*UpsertMediaRequest) ProtoMessage() {} func (x *UpsertMediaRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[4] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -308,7 +562,7 @@ func (x *UpsertMediaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertMediaRequest.ProtoReflect.Descriptor instead. func (*UpsertMediaRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{4} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{8} } func (x *UpsertMediaRequest) GetData() []*v13.MediaRecord { @@ -318,17 +572,25 @@ func (x *UpsertMediaRequest) GetData() []*v13.MediaRecord { return nil } +func (x *UpsertMediaRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // UpsertLogs 请求,异步插入一批终端输出 type UpsertLogsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Logs []*v14.LogRecord `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` // 终端日志记录列表 + Logs []*v14.LogRecord `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` // 终端日志记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpsertLogsRequest) Reset() { *x = UpsertLogsRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[5] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -340,7 +602,7 @@ func (x *UpsertLogsRequest) String() string { func (*UpsertLogsRequest) ProtoMessage() {} func (x *UpsertLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[5] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -353,7 +615,7 @@ func (x *UpsertLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLogsRequest.ProtoReflect.Descriptor instead. func (*UpsertLogsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{5} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{9} } func (x *UpsertLogsRequest) GetLogs() []*v14.LogRecord { @@ -363,17 +625,25 @@ func (x *UpsertLogsRequest) GetLogs() []*v14.LogRecord { return nil } +func (x *UpsertLogsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // UpsertSaves 请求,异步插入一批文件保存记录 type UpsertSavesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Saves []*v15.SaveRecord `protobuf:"bytes,1,rep,name=saves,proto3" json:"saves,omitempty"` // 保存记录列表 + Saves []*v15.SaveRecord `protobuf:"bytes,1,rep,name=saves,proto3" json:"saves,omitempty"` // 保存记录列表 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpsertSavesRequest) Reset() { *x = UpsertSavesRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[6] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -385,7 +655,7 @@ func (x *UpsertSavesRequest) String() string { func (*UpsertSavesRequest) ProtoMessage() {} func (x *UpsertSavesRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[6] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -398,7 +668,7 @@ func (x *UpsertSavesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertSavesRequest.ProtoReflect.Descriptor instead. func (*UpsertSavesRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{6} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{10} } func (x *UpsertSavesRequest) GetSaves() []*v15.SaveRecord { @@ -408,17 +678,25 @@ func (x *UpsertSavesRequest) GetSaves() []*v15.SaveRecord { return nil } +func (x *UpsertSavesRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // DeliverRunFinish 请求,生命周期函数,运行结束 type DeliverRunFinishRequest struct { state protoimpl.MessageState `protogen:"open.v1"` FinishRecord *v11.FinishRecord `protobuf:"bytes,1,opt,name=finish_record,json=finishRecord,proto3" json:"finish_record,omitempty"` // Run 结束记录 + RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeliverRunFinishRequest) Reset() { *x = DeliverRunFinishRequest{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[7] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -430,7 +708,7 @@ func (x *DeliverRunFinishRequest) String() string { func (*DeliverRunFinishRequest) ProtoMessage() {} func (x *DeliverRunFinishRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[7] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -443,7 +721,7 @@ func (x *DeliverRunFinishRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeliverRunFinishRequest.ProtoReflect.Descriptor instead. func (*DeliverRunFinishRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{7} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{11} } func (x *DeliverRunFinishRequest) GetFinishRecord() *v11.FinishRecord { @@ -453,6 +731,13 @@ func (x *DeliverRunFinishRequest) GetFinishRecord() *v11.FinishRecord { return nil } +func (x *DeliverRunFinishRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // DeliverRunFinish 响应 type DeliverRunFinishResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -464,7 +749,7 @@ type DeliverRunFinishResponse struct { func (x *DeliverRunFinishResponse) Reset() { *x = DeliverRunFinishResponse{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[8] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -476,7 +761,7 @@ func (x *DeliverRunFinishResponse) String() string { func (*DeliverRunFinishResponse) ProtoMessage() {} func (x *DeliverRunFinishResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[8] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -489,7 +774,7 @@ func (x *DeliverRunFinishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeliverRunFinishResponse.ProtoReflect.Descriptor instead. func (*DeliverRunFinishResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{8} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{12} } func (x *DeliverRunFinishResponse) GetSuccess() bool { @@ -506,6 +791,51 @@ func (x *DeliverRunFinishResponse) GetMessage() string { return "" } +// GetOperationStats 请求。 +type GetOperationStatsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetOperationStatsRequest) Reset() { + *x = GetOperationStatsRequest{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetOperationStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOperationStatsRequest) ProtoMessage() {} + +func (x *GetOperationStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOperationStatsRequest.ProtoReflect.Descriptor instead. +func (*GetOperationStatsRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{13} +} + +func (x *GetOperationStatsRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // GetOperationStats 相应 type GetOperationStatsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -518,7 +848,7 @@ type GetOperationStatsResponse struct { func (x *GetOperationStatsResponse) Reset() { *x = GetOperationStatsResponse{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[9] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -530,7 +860,7 @@ func (x *GetOperationStatsResponse) String() string { func (*GetOperationStatsResponse) ProtoMessage() {} func (x *GetOperationStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[9] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -543,7 +873,7 @@ func (x *GetOperationStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetOperationStatsResponse.ProtoReflect.Descriptor instead. func (*GetOperationStatsResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{9} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{14} } func (x *GetOperationStatsResponse) GetSuccess() bool { @@ -567,6 +897,51 @@ func (x *GetOperationStatsResponse) GetStats() *v16.OperationStats { return nil } +// ConfirmRunFinish 请求。 +type ConfirmRunFinishRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmRunFinishRequest) Reset() { + *x = ConfirmRunFinishRequest{} + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmRunFinishRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmRunFinishRequest) ProtoMessage() {} + +func (x *ConfirmRunFinishRequest) ProtoReflect() protoreflect.Message { + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmRunFinishRequest.ProtoReflect.Descriptor instead. +func (*ConfirmRunFinishRequest) Descriptor() ([]byte, []int) { + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{15} +} + +func (x *ConfirmRunFinishRequest) GetRunHandle() string { + if x != nil { + return x.RunHandle + } + return "" +} + // ConfirmRunFinish 响应 type ConfirmRunFinishResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -578,7 +953,7 @@ type ConfirmRunFinishResponse struct { func (x *ConfirmRunFinishResponse) Reset() { *x = ConfirmRunFinishResponse{} - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[10] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -590,7 +965,7 @@ func (x *ConfirmRunFinishResponse) String() string { func (*ConfirmRunFinishResponse) ProtoMessage() {} func (x *ConfirmRunFinishResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[10] + mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -603,7 +978,7 @@ func (x *ConfirmRunFinishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmRunFinishResponse.ProtoReflect.Descriptor instead. func (*ConfirmRunFinishResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{10} + return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{16} } func (x *ConfirmRunFinishResponse) GetSuccess() bool { @@ -624,10 +999,25 @@ var File_swanlab_grpc_core_v1_core_proto protoreflect.FileDescriptor const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "\n" + - "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\xa5\x01\n" + + "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n" + + "\x16GetCapabilitiesRequest\"\xcb\x02\n" + + "\x17GetCapabilitiesResponse\x12)\n" + + "\x10protocol_version\x18\x01 \x01(\tR\x0fprotocolVersion\x12!\n" + + "\fcore_version\x18\x02 \x01(\tR\vcoreVersion\x12\x1c\n" + + "\tlifecycle\x18\x03 \x01(\bR\tlifecycle\x12'\n" + + "\x0fsupported_modes\x18\x04 \x03(\tR\x0esupportedModes\x12\x14\n" + + "\x05store\x18\x05 \x01(\bR\x05store\x12\x1c\n" + + "\ttransport\x18\x06 \x01(\bR\ttransport\x12\x18\n" + + "\apayload\x18\a \x01(\bR\apayload\x12\x12\n" + + "\x04sync\x18\b \x01(\bR\x04sync\x129\n" + + "\x19max_receive_message_bytes\x18\t \x01(\x03R\x16maxReceiveMessageBytes\"9\n" + + "\x16TeardownServiceRequest\x12\x1f\n" + + "\vowner_token\x18\x01 \x01(\tR\n" + + "ownerToken\"\x19\n" + + "\x17TeardownServiceResponse\"\xa5\x01\n" + "\x16DeliverRunStartRequest\x12K\n" + "\rcore_settings\x18\x01 \x01(\v2&.swanlab.settings.core.v1.CoreSettingsR\fcoreSettings\x12>\n" + - "\fstart_record\x18\x02 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\vstartRecord\"\x9a\x02\n" + + "\fstart_record\x18\x02 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\vstartRecord\"\xb9\x02\n" + "\x17DeliverRunStartResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12-\n" + @@ -637,30 +1027,52 @@ const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "\vglobal_step\x18\x06 \x01(\x03R\n" + "globalStep\x12,\n" + "\x12global_system_step\x18\a \x01(\x03R\x10globalSystemStep\x12%\n" + - "\x0enew_experiment\x18\b \x01(\bR\rnewExperiment\"X\n" + + "\x0enew_experiment\x18\b \x01(\bR\rnewExperiment\x12\x1d\n" + + "\n" + + "run_handle\x18\t \x01(\tR\trunHandle\"w\n" + "\x14UpsertColumnsRequest\x12@\n" + - "\acolumns\x18\x01 \x03(\v2&.swanlab.metric.column.v1.ColumnRecordR\acolumns\"P\n" + + "\acolumns\x18\x01 \x03(\v2&.swanlab.metric.column.v1.ColumnRecordR\acolumns\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"o\n" + "\x14UpsertScalarsRequest\x128\n" + - "\x04data\x18\x01 \x03(\v2$.swanlab.metric.data.v1.ScalarRecordR\x04data\"M\n" + + "\x04data\x18\x01 \x03(\v2$.swanlab.metric.data.v1.ScalarRecordR\x04data\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"l\n" + "\x12UpsertMediaRequest\x127\n" + - "\x04data\x18\x01 \x03(\v2#.swanlab.metric.data.v1.MediaRecordR\x04data\"G\n" + + "\x04data\x18\x01 \x03(\v2#.swanlab.metric.data.v1.MediaRecordR\x04data\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + "\x11UpsertLogsRequest\x122\n" + - "\x04logs\x18\x01 \x03(\v2\x1e.swanlab.terminal.v1.LogRecordR\x04logs\"G\n" + + "\x04logs\x18\x01 \x03(\v2\x1e.swanlab.terminal.v1.LogRecordR\x04logs\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + "\x12UpsertSavesRequest\x121\n" + - "\x05saves\x18\x01 \x03(\v2\x1b.swanlab.save.v1.SaveRecordR\x05saves\"\\\n" + + "\x05saves\x18\x01 \x03(\v2\x1b.swanlab.save.v1.SaveRecordR\x05saves\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"{\n" + "\x17DeliverRunFinishRequest\x12A\n" + - "\rfinish_record\x18\x01 \x01(\v2\x1c.swanlab.run.v1.FinishRecordR\ffinishRecord\"N\n" + + "\rfinish_record\x18\x01 \x01(\v2\x1c.swanlab.run.v1.FinishRecordR\ffinishRecord\x12\x1d\n" + + "\n" + + "run_handle\x18\x02 \x01(\tR\trunHandle\"N\n" + "\x18DeliverRunFinishResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\"\x8b\x01\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + + "\x18GetOperationStatsRequest\x12\x1d\n" + + "\n" + + "run_handle\x18\x01 \x01(\tR\trunHandle\"\x8b\x01\n" + "\x19GetOperationStatsResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12:\n" + - "\x05stats\x18\x03 \x01(\v2$.swanlab.operation.v1.OperationStatsR\x05stats\"N\n" + + "\x05stats\x18\x03 \x01(\v2$.swanlab.operation.v1.OperationStatsR\x05stats\"8\n" + + "\x17ConfirmRunFinishRequest\x12\x1d\n" + + "\n" + + "run_handle\x18\x01 \x01(\tR\trunHandle\"N\n" + "\x18ConfirmRunFinishResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage2\xc5\x06\n" + + "\amessage\x18\x02 \x01(\tR\amessage2\xd4\b\n" + "\vCoreService\x12n\n" + + "\x0fGetCapabilities\x12,.swanlab.grpc.core.v1.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v1.GetCapabilitiesResponse\x12n\n" + + "\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n" + "\x0fDeliverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n" + "\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n" + "\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + @@ -668,9 +1080,9 @@ const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "\n" + "UpsertLogs\x12'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + "\vUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n" + - "\x10DeliverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12\\\n" + - "\x11GetOperationStats\x12\x16.google.protobuf.Empty\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12Z\n" + - "\x10ConfirmRunFinish\x12\x16.google.protobuf.Empty\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3" + "\x10DeliverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n" + + "\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n" + + "\x10ConfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3" var ( file_swanlab_grpc_core_v1_core_proto_rawDescOnce sync.Once @@ -684,61 +1096,71 @@ func file_swanlab_grpc_core_v1_core_proto_rawDescGZIP() []byte { return file_swanlab_grpc_core_v1_core_proto_rawDescData } -var file_swanlab_grpc_core_v1_core_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_swanlab_grpc_core_v1_core_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_swanlab_grpc_core_v1_core_proto_goTypes = []any{ - (*DeliverRunStartRequest)(nil), // 0: swanlab.grpc.core.v1.DeliverRunStartRequest - (*DeliverRunStartResponse)(nil), // 1: swanlab.grpc.core.v1.DeliverRunStartResponse - (*UpsertColumnsRequest)(nil), // 2: swanlab.grpc.core.v1.UpsertColumnsRequest - (*UpsertScalarsRequest)(nil), // 3: swanlab.grpc.core.v1.UpsertScalarsRequest - (*UpsertMediaRequest)(nil), // 4: swanlab.grpc.core.v1.UpsertMediaRequest - (*UpsertLogsRequest)(nil), // 5: swanlab.grpc.core.v1.UpsertLogsRequest - (*UpsertSavesRequest)(nil), // 6: swanlab.grpc.core.v1.UpsertSavesRequest - (*DeliverRunFinishRequest)(nil), // 7: swanlab.grpc.core.v1.DeliverRunFinishRequest - (*DeliverRunFinishResponse)(nil), // 8: swanlab.grpc.core.v1.DeliverRunFinishResponse - (*GetOperationStatsResponse)(nil), // 9: swanlab.grpc.core.v1.GetOperationStatsResponse - (*ConfirmRunFinishResponse)(nil), // 10: swanlab.grpc.core.v1.ConfirmRunFinishResponse - (*v1.CoreSettings)(nil), // 11: swanlab.settings.core.v1.CoreSettings - (*v11.StartRecord)(nil), // 12: swanlab.run.v1.StartRecord - (*v12.ColumnRecord)(nil), // 13: swanlab.metric.column.v1.ColumnRecord - (*v13.ScalarRecord)(nil), // 14: swanlab.metric.data.v1.ScalarRecord - (*v13.MediaRecord)(nil), // 15: swanlab.metric.data.v1.MediaRecord - (*v14.LogRecord)(nil), // 16: swanlab.terminal.v1.LogRecord - (*v15.SaveRecord)(nil), // 17: swanlab.save.v1.SaveRecord - (*v11.FinishRecord)(nil), // 18: swanlab.run.v1.FinishRecord - (*v16.OperationStats)(nil), // 19: swanlab.operation.v1.OperationStats - (*emptypb.Empty)(nil), // 20: google.protobuf.Empty + (*GetCapabilitiesRequest)(nil), // 0: swanlab.grpc.core.v1.GetCapabilitiesRequest + (*GetCapabilitiesResponse)(nil), // 1: swanlab.grpc.core.v1.GetCapabilitiesResponse + (*TeardownServiceRequest)(nil), // 2: swanlab.grpc.core.v1.TeardownServiceRequest + (*TeardownServiceResponse)(nil), // 3: swanlab.grpc.core.v1.TeardownServiceResponse + (*DeliverRunStartRequest)(nil), // 4: swanlab.grpc.core.v1.DeliverRunStartRequest + (*DeliverRunStartResponse)(nil), // 5: swanlab.grpc.core.v1.DeliverRunStartResponse + (*UpsertColumnsRequest)(nil), // 6: swanlab.grpc.core.v1.UpsertColumnsRequest + (*UpsertScalarsRequest)(nil), // 7: swanlab.grpc.core.v1.UpsertScalarsRequest + (*UpsertMediaRequest)(nil), // 8: swanlab.grpc.core.v1.UpsertMediaRequest + (*UpsertLogsRequest)(nil), // 9: swanlab.grpc.core.v1.UpsertLogsRequest + (*UpsertSavesRequest)(nil), // 10: swanlab.grpc.core.v1.UpsertSavesRequest + (*DeliverRunFinishRequest)(nil), // 11: swanlab.grpc.core.v1.DeliverRunFinishRequest + (*DeliverRunFinishResponse)(nil), // 12: swanlab.grpc.core.v1.DeliverRunFinishResponse + (*GetOperationStatsRequest)(nil), // 13: swanlab.grpc.core.v1.GetOperationStatsRequest + (*GetOperationStatsResponse)(nil), // 14: swanlab.grpc.core.v1.GetOperationStatsResponse + (*ConfirmRunFinishRequest)(nil), // 15: swanlab.grpc.core.v1.ConfirmRunFinishRequest + (*ConfirmRunFinishResponse)(nil), // 16: swanlab.grpc.core.v1.ConfirmRunFinishResponse + (*v1.CoreSettings)(nil), // 17: swanlab.settings.core.v1.CoreSettings + (*v11.StartRecord)(nil), // 18: swanlab.run.v1.StartRecord + (*v12.ColumnRecord)(nil), // 19: swanlab.metric.column.v1.ColumnRecord + (*v13.ScalarRecord)(nil), // 20: swanlab.metric.data.v1.ScalarRecord + (*v13.MediaRecord)(nil), // 21: swanlab.metric.data.v1.MediaRecord + (*v14.LogRecord)(nil), // 22: swanlab.terminal.v1.LogRecord + (*v15.SaveRecord)(nil), // 23: swanlab.save.v1.SaveRecord + (*v11.FinishRecord)(nil), // 24: swanlab.run.v1.FinishRecord + (*v16.OperationStats)(nil), // 25: swanlab.operation.v1.OperationStats + (*emptypb.Empty)(nil), // 26: google.protobuf.Empty } var file_swanlab_grpc_core_v1_core_proto_depIdxs = []int32{ - 11, // 0: swanlab.grpc.core.v1.DeliverRunStartRequest.core_settings:type_name -> swanlab.settings.core.v1.CoreSettings - 12, // 1: swanlab.grpc.core.v1.DeliverRunStartRequest.start_record:type_name -> swanlab.run.v1.StartRecord - 12, // 2: swanlab.grpc.core.v1.DeliverRunStartResponse.run:type_name -> swanlab.run.v1.StartRecord - 13, // 3: swanlab.grpc.core.v1.UpsertColumnsRequest.columns:type_name -> swanlab.metric.column.v1.ColumnRecord - 14, // 4: swanlab.grpc.core.v1.UpsertScalarsRequest.data:type_name -> swanlab.metric.data.v1.ScalarRecord - 15, // 5: swanlab.grpc.core.v1.UpsertMediaRequest.data:type_name -> swanlab.metric.data.v1.MediaRecord - 16, // 6: swanlab.grpc.core.v1.UpsertLogsRequest.logs:type_name -> swanlab.terminal.v1.LogRecord - 17, // 7: swanlab.grpc.core.v1.UpsertSavesRequest.saves:type_name -> swanlab.save.v1.SaveRecord - 18, // 8: swanlab.grpc.core.v1.DeliverRunFinishRequest.finish_record:type_name -> swanlab.run.v1.FinishRecord - 19, // 9: swanlab.grpc.core.v1.GetOperationStatsResponse.stats:type_name -> swanlab.operation.v1.OperationStats - 0, // 10: swanlab.grpc.core.v1.CoreService.DeliverRunStart:input_type -> swanlab.grpc.core.v1.DeliverRunStartRequest - 2, // 11: swanlab.grpc.core.v1.CoreService.UpsertColumns:input_type -> swanlab.grpc.core.v1.UpsertColumnsRequest - 3, // 12: swanlab.grpc.core.v1.CoreService.UpsertScalars:input_type -> swanlab.grpc.core.v1.UpsertScalarsRequest - 4, // 13: swanlab.grpc.core.v1.CoreService.UpsertMedia:input_type -> swanlab.grpc.core.v1.UpsertMediaRequest - 5, // 14: swanlab.grpc.core.v1.CoreService.UpsertLogs:input_type -> swanlab.grpc.core.v1.UpsertLogsRequest - 6, // 15: swanlab.grpc.core.v1.CoreService.UpsertSaves:input_type -> swanlab.grpc.core.v1.UpsertSavesRequest - 7, // 16: swanlab.grpc.core.v1.CoreService.DeliverRunFinish:input_type -> swanlab.grpc.core.v1.DeliverRunFinishRequest - 20, // 17: swanlab.grpc.core.v1.CoreService.GetOperationStats:input_type -> google.protobuf.Empty - 20, // 18: swanlab.grpc.core.v1.CoreService.ConfirmRunFinish:input_type -> google.protobuf.Empty - 1, // 19: swanlab.grpc.core.v1.CoreService.DeliverRunStart:output_type -> swanlab.grpc.core.v1.DeliverRunStartResponse - 20, // 20: swanlab.grpc.core.v1.CoreService.UpsertColumns:output_type -> google.protobuf.Empty - 20, // 21: swanlab.grpc.core.v1.CoreService.UpsertScalars:output_type -> google.protobuf.Empty - 20, // 22: swanlab.grpc.core.v1.CoreService.UpsertMedia:output_type -> google.protobuf.Empty - 20, // 23: swanlab.grpc.core.v1.CoreService.UpsertLogs:output_type -> google.protobuf.Empty - 20, // 24: swanlab.grpc.core.v1.CoreService.UpsertSaves:output_type -> google.protobuf.Empty - 8, // 25: swanlab.grpc.core.v1.CoreService.DeliverRunFinish:output_type -> swanlab.grpc.core.v1.DeliverRunFinishResponse - 9, // 26: swanlab.grpc.core.v1.CoreService.GetOperationStats:output_type -> swanlab.grpc.core.v1.GetOperationStatsResponse - 10, // 27: swanlab.grpc.core.v1.CoreService.ConfirmRunFinish:output_type -> swanlab.grpc.core.v1.ConfirmRunFinishResponse - 19, // [19:28] is the sub-list for method output_type - 10, // [10:19] is the sub-list for method input_type + 17, // 0: swanlab.grpc.core.v1.DeliverRunStartRequest.core_settings:type_name -> swanlab.settings.core.v1.CoreSettings + 18, // 1: swanlab.grpc.core.v1.DeliverRunStartRequest.start_record:type_name -> swanlab.run.v1.StartRecord + 18, // 2: swanlab.grpc.core.v1.DeliverRunStartResponse.run:type_name -> swanlab.run.v1.StartRecord + 19, // 3: swanlab.grpc.core.v1.UpsertColumnsRequest.columns:type_name -> swanlab.metric.column.v1.ColumnRecord + 20, // 4: swanlab.grpc.core.v1.UpsertScalarsRequest.data:type_name -> swanlab.metric.data.v1.ScalarRecord + 21, // 5: swanlab.grpc.core.v1.UpsertMediaRequest.data:type_name -> swanlab.metric.data.v1.MediaRecord + 22, // 6: swanlab.grpc.core.v1.UpsertLogsRequest.logs:type_name -> swanlab.terminal.v1.LogRecord + 23, // 7: swanlab.grpc.core.v1.UpsertSavesRequest.saves:type_name -> swanlab.save.v1.SaveRecord + 24, // 8: swanlab.grpc.core.v1.DeliverRunFinishRequest.finish_record:type_name -> swanlab.run.v1.FinishRecord + 25, // 9: swanlab.grpc.core.v1.GetOperationStatsResponse.stats:type_name -> swanlab.operation.v1.OperationStats + 0, // 10: swanlab.grpc.core.v1.CoreService.GetCapabilities:input_type -> swanlab.grpc.core.v1.GetCapabilitiesRequest + 2, // 11: swanlab.grpc.core.v1.CoreService.TeardownService:input_type -> swanlab.grpc.core.v1.TeardownServiceRequest + 4, // 12: swanlab.grpc.core.v1.CoreService.DeliverRunStart:input_type -> swanlab.grpc.core.v1.DeliverRunStartRequest + 6, // 13: swanlab.grpc.core.v1.CoreService.UpsertColumns:input_type -> swanlab.grpc.core.v1.UpsertColumnsRequest + 7, // 14: swanlab.grpc.core.v1.CoreService.UpsertScalars:input_type -> swanlab.grpc.core.v1.UpsertScalarsRequest + 8, // 15: swanlab.grpc.core.v1.CoreService.UpsertMedia:input_type -> swanlab.grpc.core.v1.UpsertMediaRequest + 9, // 16: swanlab.grpc.core.v1.CoreService.UpsertLogs:input_type -> swanlab.grpc.core.v1.UpsertLogsRequest + 10, // 17: swanlab.grpc.core.v1.CoreService.UpsertSaves:input_type -> swanlab.grpc.core.v1.UpsertSavesRequest + 11, // 18: swanlab.grpc.core.v1.CoreService.DeliverRunFinish:input_type -> swanlab.grpc.core.v1.DeliverRunFinishRequest + 13, // 19: swanlab.grpc.core.v1.CoreService.GetOperationStats:input_type -> swanlab.grpc.core.v1.GetOperationStatsRequest + 15, // 20: swanlab.grpc.core.v1.CoreService.ConfirmRunFinish:input_type -> swanlab.grpc.core.v1.ConfirmRunFinishRequest + 1, // 21: swanlab.grpc.core.v1.CoreService.GetCapabilities:output_type -> swanlab.grpc.core.v1.GetCapabilitiesResponse + 3, // 22: swanlab.grpc.core.v1.CoreService.TeardownService:output_type -> swanlab.grpc.core.v1.TeardownServiceResponse + 5, // 23: swanlab.grpc.core.v1.CoreService.DeliverRunStart:output_type -> swanlab.grpc.core.v1.DeliverRunStartResponse + 26, // 24: swanlab.grpc.core.v1.CoreService.UpsertColumns:output_type -> google.protobuf.Empty + 26, // 25: swanlab.grpc.core.v1.CoreService.UpsertScalars:output_type -> google.protobuf.Empty + 26, // 26: swanlab.grpc.core.v1.CoreService.UpsertMedia:output_type -> google.protobuf.Empty + 26, // 27: swanlab.grpc.core.v1.CoreService.UpsertLogs:output_type -> google.protobuf.Empty + 26, // 28: swanlab.grpc.core.v1.CoreService.UpsertSaves:output_type -> google.protobuf.Empty + 12, // 29: swanlab.grpc.core.v1.CoreService.DeliverRunFinish:output_type -> swanlab.grpc.core.v1.DeliverRunFinishResponse + 14, // 30: swanlab.grpc.core.v1.CoreService.GetOperationStats:output_type -> swanlab.grpc.core.v1.GetOperationStatsResponse + 16, // 31: swanlab.grpc.core.v1.CoreService.ConfirmRunFinish:output_type -> swanlab.grpc.core.v1.ConfirmRunFinishResponse + 21, // [21:32] is the sub-list for method output_type + 10, // [10:21] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name @@ -755,7 +1177,7 @@ func file_swanlab_grpc_core_v1_core_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_swanlab_grpc_core_v1_core_proto_rawDesc), len(file_swanlab_grpc_core_v1_core_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go index a002549a9..c119e8cf1 100644 --- a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go @@ -20,6 +20,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + CoreService_GetCapabilities_FullMethodName = "/swanlab.grpc.core.v1.CoreService/GetCapabilities" + CoreService_TeardownService_FullMethodName = "/swanlab.grpc.core.v1.CoreService/TeardownService" CoreService_DeliverRunStart_FullMethodName = "/swanlab.grpc.core.v1.CoreService/DeliverRunStart" CoreService_UpsertColumns_FullMethodName = "/swanlab.grpc.core.v1.CoreService/UpsertColumns" CoreService_UpsertScalars_FullMethodName = "/swanlab.grpc.core.v1.CoreService/UpsertScalars" @@ -35,8 +37,20 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// CoreService 是核心业务接口,用于同步或异步地接收实验记录 +// CoreService 是核心业务接口,用于同步或异步地接收实验记录。 +// +// 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceClient interface { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 @@ -51,10 +65,10 @@ type CoreServiceClient interface { UpsertSaves(ctx context.Context, in *UpsertSavesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) - // GetOperationStats 返回 Core 当前运行状态和上传进度快照。 - GetOperationStats(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认 Core 运行结束,可以安全退出 - ConfirmRunFinish(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) } type coreServiceClient struct { @@ -65,6 +79,26 @@ func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient { return &coreServiceClient{cc} } +func (c *coreServiceClient) GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCapabilitiesResponse) + err := c.cc.Invoke(ctx, CoreService_GetCapabilities_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreServiceClient) TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TeardownServiceResponse) + err := c.cc.Invoke(ctx, CoreService_TeardownService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *coreServiceClient) DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeliverRunStartResponse) @@ -135,7 +169,7 @@ func (c *coreServiceClient) DeliverRunFinish(ctx context.Context, in *DeliverRun return out, nil } -func (c *coreServiceClient) GetOperationStats(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) { +func (c *coreServiceClient) GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetOperationStatsResponse) err := c.cc.Invoke(ctx, CoreService_GetOperationStats_FullMethodName, in, out, cOpts...) @@ -145,7 +179,7 @@ func (c *coreServiceClient) GetOperationStats(ctx context.Context, in *emptypb.E return out, nil } -func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) { +func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ConfirmRunFinishResponse) err := c.cc.Invoke(ctx, CoreService_ConfirmRunFinish_FullMethodName, in, out, cOpts...) @@ -159,8 +193,20 @@ func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *emptypb.Em // All implementations must embed UnimplementedCoreServiceServer // for forward compatibility. // -// CoreService 是核心业务接口,用于同步或异步地接收实验记录 +// CoreService 是核心业务接口,用于同步或异步地接收实验记录。 +// +// 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceServer interface { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 @@ -175,10 +221,10 @@ type CoreServiceServer interface { UpsertSaves(context.Context, *UpsertSavesRequest) (*emptypb.Empty, error) // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) - // GetOperationStats 返回 Core 当前运行状态和上传进度快照。 - GetOperationStats(context.Context, *emptypb.Empty) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认 Core 运行结束,可以安全退出 - ConfirmRunFinish(context.Context, *emptypb.Empty) (*ConfirmRunFinishResponse, error) + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) mustEmbedUnimplementedCoreServiceServer() } @@ -189,6 +235,12 @@ type CoreServiceServer interface { // pointer dereference when methods are called. type UnimplementedCoreServiceServer struct{} +func (UnimplementedCoreServiceServer) GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCapabilities not implemented") +} +func (UnimplementedCoreServiceServer) TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method TeardownService not implemented") +} func (UnimplementedCoreServiceServer) DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeliverRunStart not implemented") } @@ -210,10 +262,10 @@ func (UnimplementedCoreServiceServer) UpsertSaves(context.Context, *UpsertSavesR func (UnimplementedCoreServiceServer) DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeliverRunFinish not implemented") } -func (UnimplementedCoreServiceServer) GetOperationStats(context.Context, *emptypb.Empty) (*GetOperationStatsResponse, error) { +func (UnimplementedCoreServiceServer) GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetOperationStats not implemented") } -func (UnimplementedCoreServiceServer) ConfirmRunFinish(context.Context, *emptypb.Empty) (*ConfirmRunFinishResponse, error) { +func (UnimplementedCoreServiceServer) ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) { return nil, status.Error(codes.Unimplemented, "method ConfirmRunFinish not implemented") } func (UnimplementedCoreServiceServer) mustEmbedUnimplementedCoreServiceServer() {} @@ -237,6 +289,42 @@ func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) { s.RegisterService(&CoreService_ServiceDesc, srv) } +func _CoreService_GetCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCapabilitiesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).GetCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_GetCapabilities_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).GetCapabilities(ctx, req.(*GetCapabilitiesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CoreService_TeardownService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TeardownServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServiceServer).TeardownService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CoreService_TeardownService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServiceServer).TeardownService(ctx, req.(*TeardownServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _CoreService_DeliverRunStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeliverRunStartRequest) if err := dec(in); err != nil { @@ -364,7 +452,7 @@ func _CoreService_DeliverRunFinish_Handler(srv interface{}, ctx context.Context, } func _CoreService_GetOperationStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) + in := new(GetOperationStatsRequest) if err := dec(in); err != nil { return nil, err } @@ -376,13 +464,13 @@ func _CoreService_GetOperationStats_Handler(srv interface{}, ctx context.Context FullMethod: CoreService_GetOperationStats_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).GetOperationStats(ctx, req.(*emptypb.Empty)) + return srv.(CoreServiceServer).GetOperationStats(ctx, req.(*GetOperationStatsRequest)) } return interceptor(ctx, in, info, handler) } func _CoreService_ConfirmRunFinish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) + in := new(ConfirmRunFinishRequest) if err := dec(in); err != nil { return nil, err } @@ -394,7 +482,7 @@ func _CoreService_ConfirmRunFinish_Handler(srv interface{}, ctx context.Context, FullMethod: CoreService_ConfirmRunFinish_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ConfirmRunFinish(ctx, req.(*emptypb.Empty)) + return srv.(CoreServiceServer).ConfirmRunFinish(ctx, req.(*ConfirmRunFinishRequest)) } return interceptor(ctx, in, info, handler) } @@ -406,6 +494,14 @@ var CoreService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "swanlab.grpc.core.v1.CoreService", HandlerType: (*CoreServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "GetCapabilities", + Handler: _CoreService_GetCapabilities_Handler, + }, + { + MethodName: "TeardownService", + Handler: _CoreService_TeardownService_Handler, + }, { MethodName: "DeliverRunStart", Handler: _CoreService_DeliverRunStart_Handler, diff --git a/core/proto/swanlab/grpc/core/v2/core.pb.go b/core/proto/swanlab/grpc/core/v2/core.pb.go deleted file mode 100644 index a2d225cd8..000000000 --- a/core/proto/swanlab/grpc/core/v2/core.pb.go +++ /dev/null @@ -1,1191 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v7.35.1 -// source: swanlab/grpc/core/v2/core.proto - -package corev2 - -import ( - v12 "github.com/swanhubx/swanlab/core/proto/swanlab/metric/column/v1" - v13 "github.com/swanhubx/swanlab/core/proto/swanlab/metric/data/v1" - v16 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" - v11 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" - v15 "github.com/swanhubx/swanlab/core/proto/swanlab/save/v1" - v1 "github.com/swanhubx/swanlab/core/proto/swanlab/settings/core/v1" - v14 "github.com/swanhubx/swanlab/core/proto/swanlab/terminal/v1" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// GetCapabilities 请求。当前无字段,预留后续扩展。 -type GetCapabilitiesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetCapabilitiesRequest) Reset() { - *x = GetCapabilitiesRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetCapabilitiesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCapabilitiesRequest) ProtoMessage() {} - -func (x *GetCapabilitiesRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCapabilitiesRequest.ProtoReflect.Descriptor instead. -func (*GetCapabilitiesRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{0} -} - -// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, -// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 -type GetCapabilitiesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProtocolVersion string `protobuf:"bytes,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` // 协议版本,与 Python SDK 期望版本锁步 - CoreVersion string `protobuf:"bytes,2,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` // core 二进制编译版本(构建管线注入) - Lifecycle bool `protobuf:"varint,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"` // 生命周期与进程管理能力 - SupportedModes []string `protobuf:"bytes,4,rep,name=supported_modes,json=supportedModes,proto3" json:"supported_modes,omitempty"` // 支持的 run 模式,如 online/local/offline - Store bool `protobuf:"varint,5,opt,name=store,proto3" json:"store,omitempty"` // 本地持久化能力 - Transport bool `protobuf:"varint,6,opt,name=transport,proto3" json:"transport,omitempty"` // 云端上传能力 - Payload bool `protobuf:"varint,7,opt,name=payload,proto3" json:"payload,omitempty"` // 内联 payload 能力(online skip_store 模式) - Sync bool `protobuf:"varint,8,opt,name=sync,proto3" json:"sync,omitempty"` // 离线数据同步能力 - MaxReceiveMessageBytes int64 `protobuf:"varint,9,opt,name=max_receive_message_bytes,json=maxReceiveMessageBytes,proto3" json:"max_receive_message_bytes,omitempty"` // 服务端单条消息接收上限,用于客户端协商 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetCapabilitiesResponse) Reset() { - *x = GetCapabilitiesResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetCapabilitiesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCapabilitiesResponse) ProtoMessage() {} - -func (x *GetCapabilitiesResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCapabilitiesResponse.ProtoReflect.Descriptor instead. -func (*GetCapabilitiesResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{1} -} - -func (x *GetCapabilitiesResponse) GetProtocolVersion() string { - if x != nil { - return x.ProtocolVersion - } - return "" -} - -func (x *GetCapabilitiesResponse) GetCoreVersion() string { - if x != nil { - return x.CoreVersion - } - return "" -} - -func (x *GetCapabilitiesResponse) GetLifecycle() bool { - if x != nil { - return x.Lifecycle - } - return false -} - -func (x *GetCapabilitiesResponse) GetSupportedModes() []string { - if x != nil { - return x.SupportedModes - } - return nil -} - -func (x *GetCapabilitiesResponse) GetStore() bool { - if x != nil { - return x.Store - } - return false -} - -func (x *GetCapabilitiesResponse) GetTransport() bool { - if x != nil { - return x.Transport - } - return false -} - -func (x *GetCapabilitiesResponse) GetPayload() bool { - if x != nil { - return x.Payload - } - return false -} - -func (x *GetCapabilitiesResponse) GetSync() bool { - if x != nil { - return x.Sync - } - return false -} - -func (x *GetCapabilitiesResponse) GetMaxReceiveMessageBytes() int64 { - if x != nil { - return x.MaxReceiveMessageBytes - } - return 0 -} - -// TeardownService 请求。 -type TeardownServiceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TeardownServiceRequest) Reset() { - *x = TeardownServiceRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TeardownServiceRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TeardownServiceRequest) ProtoMessage() {} - -func (x *TeardownServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TeardownServiceRequest.ProtoReflect.Descriptor instead. -func (*TeardownServiceRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{2} -} - -func (x *TeardownServiceRequest) GetOwnerToken() string { - if x != nil { - return x.OwnerToken - } - return "" -} - -// TeardownService 响应。 -type TeardownServiceResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TeardownServiceResponse) Reset() { - *x = TeardownServiceResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TeardownServiceResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TeardownServiceResponse) ProtoMessage() {} - -func (x *TeardownServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TeardownServiceResponse.ProtoReflect.Descriptor instead. -func (*TeardownServiceResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{3} -} - -// DeliverRunStart 请求,生命周期函数,运行正式开始 -type DeliverRunStartRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - CoreSettings *v1.CoreSettings `protobuf:"bytes,1,opt,name=core_settings,json=coreSettings,proto3" json:"core_settings,omitempty"` // Run 服务配置 - StartRecord *v11.StartRecord `protobuf:"bytes,2,opt,name=start_record,json=startRecord,proto3" json:"start_record,omitempty"` // Run 创建记录 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeliverRunStartRequest) Reset() { - *x = DeliverRunStartRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeliverRunStartRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeliverRunStartRequest) ProtoMessage() {} - -func (x *DeliverRunStartRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeliverRunStartRequest.ProtoReflect.Descriptor instead. -func (*DeliverRunStartRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{4} -} - -func (x *DeliverRunStartRequest) GetCoreSettings() *v1.CoreSettings { - if x != nil { - return x.CoreSettings - } - return nil -} - -func (x *DeliverRunStartRequest) GetStartRecord() *v11.StartRecord { - if x != nil { - return x.StartRecord - } - return nil -} - -// DeliverRunStart 响应 -type DeliverRunStartResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 - Run *v11.StartRecord `protobuf:"bytes,3,opt,name=run,proto3" json:"run,omitempty"` // 最终创建的 Run 记录 - Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) - Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` // 对应的实验名称 - GlobalStep int64 `protobuf:"varint,6,opt,name=global_step,json=globalStep,proto3" json:"global_step,omitempty"` // 起始全局步数 - GlobalSystemStep int64 `protobuf:"varint,7,opt,name=global_system_step,json=globalSystemStep,proto3" json:"global_system_step,omitempty"` // 起始全局系统步数 - NewExperiment bool `protobuf:"varint,8,opt,name=new_experiment,json=newExperiment,proto3" json:"new_experiment,omitempty"` // 是否是新实验 - RunHandle string `protobuf:"bytes,9,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeliverRunStartResponse) Reset() { - *x = DeliverRunStartResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeliverRunStartResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeliverRunStartResponse) ProtoMessage() {} - -func (x *DeliverRunStartResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeliverRunStartResponse.ProtoReflect.Descriptor instead. -func (*DeliverRunStartResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{5} -} - -func (x *DeliverRunStartResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *DeliverRunStartResponse) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -func (x *DeliverRunStartResponse) GetRun() *v11.StartRecord { - if x != nil { - return x.Run - } - return nil -} - -func (x *DeliverRunStartResponse) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -func (x *DeliverRunStartResponse) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *DeliverRunStartResponse) GetGlobalStep() int64 { - if x != nil { - return x.GlobalStep - } - return 0 -} - -func (x *DeliverRunStartResponse) GetGlobalSystemStep() int64 { - if x != nil { - return x.GlobalSystemStep - } - return 0 -} - -func (x *DeliverRunStartResponse) GetNewExperiment() bool { - if x != nil { - return x.NewExperiment - } - return false -} - -func (x *DeliverRunStartResponse) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// UpsertColumns 请求,异步插入一批列定义 -type UpsertColumnsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Columns []*v12.ColumnRecord `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` // 列定义列表 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpsertColumnsRequest) Reset() { - *x = UpsertColumnsRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpsertColumnsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpsertColumnsRequest) ProtoMessage() {} - -func (x *UpsertColumnsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpsertColumnsRequest.ProtoReflect.Descriptor instead. -func (*UpsertColumnsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{6} -} - -func (x *UpsertColumnsRequest) GetColumns() []*v12.ColumnRecord { - if x != nil { - return x.Columns - } - return nil -} - -func (x *UpsertColumnsRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// UpsertScalars 请求,异步插入一批标量数据 -type UpsertScalarsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Data []*v13.ScalarRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 标量记录列表 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpsertScalarsRequest) Reset() { - *x = UpsertScalarsRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpsertScalarsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpsertScalarsRequest) ProtoMessage() {} - -func (x *UpsertScalarsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpsertScalarsRequest.ProtoReflect.Descriptor instead. -func (*UpsertScalarsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{7} -} - -func (x *UpsertScalarsRequest) GetData() []*v13.ScalarRecord { - if x != nil { - return x.Data - } - return nil -} - -func (x *UpsertScalarsRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// UpsertMedia 请求,异步插入一批媒体数据 -type UpsertMediaRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Data []*v13.MediaRecord `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` // 媒体记录列表 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpsertMediaRequest) Reset() { - *x = UpsertMediaRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpsertMediaRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpsertMediaRequest) ProtoMessage() {} - -func (x *UpsertMediaRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpsertMediaRequest.ProtoReflect.Descriptor instead. -func (*UpsertMediaRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{8} -} - -func (x *UpsertMediaRequest) GetData() []*v13.MediaRecord { - if x != nil { - return x.Data - } - return nil -} - -func (x *UpsertMediaRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// UpsertLogs 请求,异步插入一批终端输出 -type UpsertLogsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Logs []*v14.LogRecord `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` // 终端日志记录列表 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpsertLogsRequest) Reset() { - *x = UpsertLogsRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpsertLogsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpsertLogsRequest) ProtoMessage() {} - -func (x *UpsertLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpsertLogsRequest.ProtoReflect.Descriptor instead. -func (*UpsertLogsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{9} -} - -func (x *UpsertLogsRequest) GetLogs() []*v14.LogRecord { - if x != nil { - return x.Logs - } - return nil -} - -func (x *UpsertLogsRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// UpsertSaves 请求,异步插入一批文件保存记录 -type UpsertSavesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Saves []*v15.SaveRecord `protobuf:"bytes,1,rep,name=saves,proto3" json:"saves,omitempty"` // 保存记录列表 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *UpsertSavesRequest) Reset() { - *x = UpsertSavesRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *UpsertSavesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpsertSavesRequest) ProtoMessage() {} - -func (x *UpsertSavesRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpsertSavesRequest.ProtoReflect.Descriptor instead. -func (*UpsertSavesRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{10} -} - -func (x *UpsertSavesRequest) GetSaves() []*v15.SaveRecord { - if x != nil { - return x.Saves - } - return nil -} - -func (x *UpsertSavesRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// DeliverRunFinish 请求,生命周期函数,运行结束 -type DeliverRunFinishRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - FinishRecord *v11.FinishRecord `protobuf:"bytes,1,opt,name=finish_record,json=finishRecord,proto3" json:"finish_record,omitempty"` // Run 结束记录 - RunHandle string `protobuf:"bytes,2,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeliverRunFinishRequest) Reset() { - *x = DeliverRunFinishRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeliverRunFinishRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeliverRunFinishRequest) ProtoMessage() {} - -func (x *DeliverRunFinishRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeliverRunFinishRequest.ProtoReflect.Descriptor instead. -func (*DeliverRunFinishRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{11} -} - -func (x *DeliverRunFinishRequest) GetFinishRecord() *v11.FinishRecord { - if x != nil { - return x.FinishRecord - } - return nil -} - -func (x *DeliverRunFinishRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// DeliverRunFinish 响应 -type DeliverRunFinishResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DeliverRunFinishResponse) Reset() { - *x = DeliverRunFinishResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DeliverRunFinishResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeliverRunFinishResponse) ProtoMessage() {} - -func (x *DeliverRunFinishResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeliverRunFinishResponse.ProtoReflect.Descriptor instead. -func (*DeliverRunFinishResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{12} -} - -func (x *DeliverRunFinishResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *DeliverRunFinishResponse) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -// GetOperationStats 请求。 -type GetOperationStatsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetOperationStatsRequest) Reset() { - *x = GetOperationStatsRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetOperationStatsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOperationStatsRequest) ProtoMessage() {} - -func (x *GetOperationStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOperationStatsRequest.ProtoReflect.Descriptor instead. -func (*GetOperationStatsRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{13} -} - -func (x *GetOperationStatsRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// GetOperationStats 响应 -type GetOperationStatsResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 - Stats *v16.OperationStats `protobuf:"bytes,3,opt,name=stats,proto3" json:"stats,omitempty"` // Core 当前运行状态和上传进度快照 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetOperationStatsResponse) Reset() { - *x = GetOperationStatsResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetOperationStatsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOperationStatsResponse) ProtoMessage() {} - -func (x *GetOperationStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[14] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOperationStatsResponse.ProtoReflect.Descriptor instead. -func (*GetOperationStatsResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{14} -} - -func (x *GetOperationStatsResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *GetOperationStatsResponse) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -func (x *GetOperationStatsResponse) GetStats() *v16.OperationStats { - if x != nil { - return x.Stats - } - return nil -} - -// ConfirmRunFinish 请求。 -type ConfirmRunFinishRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - RunHandle string `protobuf:"bytes,1,opt,name=run_handle,json=runHandle,proto3" json:"run_handle,omitempty"` // 目标 run 会话标识 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ConfirmRunFinishRequest) Reset() { - *x = ConfirmRunFinishRequest{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ConfirmRunFinishRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConfirmRunFinishRequest) ProtoMessage() {} - -func (x *ConfirmRunFinishRequest) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[15] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConfirmRunFinishRequest.ProtoReflect.Descriptor instead. -func (*ConfirmRunFinishRequest) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{15} -} - -func (x *ConfirmRunFinishRequest) GetRunHandle() string { - if x != nil { - return x.RunHandle - } - return "" -} - -// ConfirmRunFinish 响应 -type ConfirmRunFinishResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 请求是否成功 - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 请求失败的响应 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ConfirmRunFinishResponse) Reset() { - *x = ConfirmRunFinishResponse{} - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ConfirmRunFinishResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConfirmRunFinishResponse) ProtoMessage() {} - -func (x *ConfirmRunFinishResponse) ProtoReflect() protoreflect.Message { - mi := &file_swanlab_grpc_core_v2_core_proto_msgTypes[16] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConfirmRunFinishResponse.ProtoReflect.Descriptor instead. -func (*ConfirmRunFinishResponse) Descriptor() ([]byte, []int) { - return file_swanlab_grpc_core_v2_core_proto_rawDescGZIP(), []int{16} -} - -func (x *ConfirmRunFinishResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *ConfirmRunFinishResponse) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -var File_swanlab_grpc_core_v2_core_proto protoreflect.FileDescriptor - -const file_swanlab_grpc_core_v2_core_proto_rawDesc = "" + - "\n" + - "\x1fswanlab/grpc/core/v2/core.proto\x12\x14swanlab.grpc.core.v2\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n" + - "\x16GetCapabilitiesRequest\"\xcb\x02\n" + - "\x17GetCapabilitiesResponse\x12)\n" + - "\x10protocol_version\x18\x01 \x01(\tR\x0fprotocolVersion\x12!\n" + - "\fcore_version\x18\x02 \x01(\tR\vcoreVersion\x12\x1c\n" + - "\tlifecycle\x18\x03 \x01(\bR\tlifecycle\x12'\n" + - "\x0fsupported_modes\x18\x04 \x03(\tR\x0esupportedModes\x12\x14\n" + - "\x05store\x18\x05 \x01(\bR\x05store\x12\x1c\n" + - "\ttransport\x18\x06 \x01(\bR\ttransport\x12\x18\n" + - "\apayload\x18\a \x01(\bR\apayload\x12\x12\n" + - "\x04sync\x18\b \x01(\bR\x04sync\x129\n" + - "\x19max_receive_message_bytes\x18\t \x01(\x03R\x16maxReceiveMessageBytes\"9\n" + - "\x16TeardownServiceRequest\x12\x1f\n" + - "\vowner_token\x18\x01 \x01(\tR\n" + - "ownerToken\"\x19\n" + - "\x17TeardownServiceResponse\"\xa5\x01\n" + - "\x16DeliverRunStartRequest\x12K\n" + - "\rcore_settings\x18\x01 \x01(\v2&.swanlab.settings.core.v1.CoreSettingsR\fcoreSettings\x12>\n" + - "\fstart_record\x18\x02 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\vstartRecord\"\xb9\x02\n" + - "\x17DeliverRunStartResponse\x12\x18\n" + - "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\x12-\n" + - "\x03run\x18\x03 \x01(\v2\x1b.swanlab.run.v1.StartRecordR\x03run\x12\x12\n" + - "\x04path\x18\x04 \x01(\tR\x04path\x12\x12\n" + - "\x04name\x18\x05 \x01(\tR\x04name\x12\x1f\n" + - "\vglobal_step\x18\x06 \x01(\x03R\n" + - "globalStep\x12,\n" + - "\x12global_system_step\x18\a \x01(\x03R\x10globalSystemStep\x12%\n" + - "\x0enew_experiment\x18\b \x01(\bR\rnewExperiment\x12\x1d\n" + - "\n" + - "run_handle\x18\t \x01(\tR\trunHandle\"w\n" + - "\x14UpsertColumnsRequest\x12@\n" + - "\acolumns\x18\x01 \x03(\v2&.swanlab.metric.column.v1.ColumnRecordR\acolumns\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"o\n" + - "\x14UpsertScalarsRequest\x128\n" + - "\x04data\x18\x01 \x03(\v2$.swanlab.metric.data.v1.ScalarRecordR\x04data\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"l\n" + - "\x12UpsertMediaRequest\x127\n" + - "\x04data\x18\x01 \x03(\v2#.swanlab.metric.data.v1.MediaRecordR\x04data\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + - "\x11UpsertLogsRequest\x122\n" + - "\x04logs\x18\x01 \x03(\v2\x1e.swanlab.terminal.v1.LogRecordR\x04logs\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"f\n" + - "\x12UpsertSavesRequest\x121\n" + - "\x05saves\x18\x01 \x03(\v2\x1b.swanlab.save.v1.SaveRecordR\x05saves\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"{\n" + - "\x17DeliverRunFinishRequest\x12A\n" + - "\rfinish_record\x18\x01 \x01(\v2\x1c.swanlab.run.v1.FinishRecordR\ffinishRecord\x12\x1d\n" + - "\n" + - "run_handle\x18\x02 \x01(\tR\trunHandle\"N\n" + - "\x18DeliverRunFinishResponse\x12\x18\n" + - "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + - "\x18GetOperationStatsRequest\x12\x1d\n" + - "\n" + - "run_handle\x18\x01 \x01(\tR\trunHandle\"\x8b\x01\n" + - "\x19GetOperationStatsResponse\x12\x18\n" + - "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage\x12:\n" + - "\x05stats\x18\x03 \x01(\v2$.swanlab.operation.v1.OperationStatsR\x05stats\"8\n" + - "\x17ConfirmRunFinishRequest\x12\x1d\n" + - "\n" + - "run_handle\x18\x01 \x01(\tR\trunHandle\"N\n" + - "\x18ConfirmRunFinishResponse\x12\x18\n" + - "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage2\xd4\b\n" + - "\vCoreService\x12n\n" + - "\x0fGetCapabilities\x12,.swanlab.grpc.core.v2.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v2.GetCapabilitiesResponse\x12n\n" + - "\x0fTeardownService\x12,.swanlab.grpc.core.v2.TeardownServiceRequest\x1a-.swanlab.grpc.core.v2.TeardownServiceResponse\x12n\n" + - "\x0fDeliverRunStart\x12,.swanlab.grpc.core.v2.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v2.DeliverRunStartResponse\x12S\n" + - "\rUpsertColumns\x12*.swanlab.grpc.core.v2.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n" + - "\rUpsertScalars\x12*.swanlab.grpc.core.v2.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + - "\vUpsertMedia\x12(.swanlab.grpc.core.v2.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n" + - "\n" + - "UpsertLogs\x12'.swanlab.grpc.core.v2.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n" + - "\vUpsertSaves\x12(.swanlab.grpc.core.v2.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n" + - "\x10DeliverRunFinish\x12-.swanlab.grpc.core.v2.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v2.DeliverRunFinishResponse\x12t\n" + - "\x11GetOperationStats\x12..swanlab.grpc.core.v2.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v2.GetOperationStatsResponse\x12q\n" + - "\x10ConfirmRunFinish\x12-.swanlab.grpc.core.v2.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v2.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2b\x06proto3" - -var ( - file_swanlab_grpc_core_v2_core_proto_rawDescOnce sync.Once - file_swanlab_grpc_core_v2_core_proto_rawDescData []byte -) - -func file_swanlab_grpc_core_v2_core_proto_rawDescGZIP() []byte { - file_swanlab_grpc_core_v2_core_proto_rawDescOnce.Do(func() { - file_swanlab_grpc_core_v2_core_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_swanlab_grpc_core_v2_core_proto_rawDesc), len(file_swanlab_grpc_core_v2_core_proto_rawDesc))) - }) - return file_swanlab_grpc_core_v2_core_proto_rawDescData -} - -var file_swanlab_grpc_core_v2_core_proto_msgTypes = make([]protoimpl.MessageInfo, 17) -var file_swanlab_grpc_core_v2_core_proto_goTypes = []any{ - (*GetCapabilitiesRequest)(nil), // 0: swanlab.grpc.core.v2.GetCapabilitiesRequest - (*GetCapabilitiesResponse)(nil), // 1: swanlab.grpc.core.v2.GetCapabilitiesResponse - (*TeardownServiceRequest)(nil), // 2: swanlab.grpc.core.v2.TeardownServiceRequest - (*TeardownServiceResponse)(nil), // 3: swanlab.grpc.core.v2.TeardownServiceResponse - (*DeliverRunStartRequest)(nil), // 4: swanlab.grpc.core.v2.DeliverRunStartRequest - (*DeliverRunStartResponse)(nil), // 5: swanlab.grpc.core.v2.DeliverRunStartResponse - (*UpsertColumnsRequest)(nil), // 6: swanlab.grpc.core.v2.UpsertColumnsRequest - (*UpsertScalarsRequest)(nil), // 7: swanlab.grpc.core.v2.UpsertScalarsRequest - (*UpsertMediaRequest)(nil), // 8: swanlab.grpc.core.v2.UpsertMediaRequest - (*UpsertLogsRequest)(nil), // 9: swanlab.grpc.core.v2.UpsertLogsRequest - (*UpsertSavesRequest)(nil), // 10: swanlab.grpc.core.v2.UpsertSavesRequest - (*DeliverRunFinishRequest)(nil), // 11: swanlab.grpc.core.v2.DeliverRunFinishRequest - (*DeliverRunFinishResponse)(nil), // 12: swanlab.grpc.core.v2.DeliverRunFinishResponse - (*GetOperationStatsRequest)(nil), // 13: swanlab.grpc.core.v2.GetOperationStatsRequest - (*GetOperationStatsResponse)(nil), // 14: swanlab.grpc.core.v2.GetOperationStatsResponse - (*ConfirmRunFinishRequest)(nil), // 15: swanlab.grpc.core.v2.ConfirmRunFinishRequest - (*ConfirmRunFinishResponse)(nil), // 16: swanlab.grpc.core.v2.ConfirmRunFinishResponse - (*v1.CoreSettings)(nil), // 17: swanlab.settings.core.v1.CoreSettings - (*v11.StartRecord)(nil), // 18: swanlab.run.v1.StartRecord - (*v12.ColumnRecord)(nil), // 19: swanlab.metric.column.v1.ColumnRecord - (*v13.ScalarRecord)(nil), // 20: swanlab.metric.data.v1.ScalarRecord - (*v13.MediaRecord)(nil), // 21: swanlab.metric.data.v1.MediaRecord - (*v14.LogRecord)(nil), // 22: swanlab.terminal.v1.LogRecord - (*v15.SaveRecord)(nil), // 23: swanlab.save.v1.SaveRecord - (*v11.FinishRecord)(nil), // 24: swanlab.run.v1.FinishRecord - (*v16.OperationStats)(nil), // 25: swanlab.operation.v1.OperationStats - (*emptypb.Empty)(nil), // 26: google.protobuf.Empty -} -var file_swanlab_grpc_core_v2_core_proto_depIdxs = []int32{ - 17, // 0: swanlab.grpc.core.v2.DeliverRunStartRequest.core_settings:type_name -> swanlab.settings.core.v1.CoreSettings - 18, // 1: swanlab.grpc.core.v2.DeliverRunStartRequest.start_record:type_name -> swanlab.run.v1.StartRecord - 18, // 2: swanlab.grpc.core.v2.DeliverRunStartResponse.run:type_name -> swanlab.run.v1.StartRecord - 19, // 3: swanlab.grpc.core.v2.UpsertColumnsRequest.columns:type_name -> swanlab.metric.column.v1.ColumnRecord - 20, // 4: swanlab.grpc.core.v2.UpsertScalarsRequest.data:type_name -> swanlab.metric.data.v1.ScalarRecord - 21, // 5: swanlab.grpc.core.v2.UpsertMediaRequest.data:type_name -> swanlab.metric.data.v1.MediaRecord - 22, // 6: swanlab.grpc.core.v2.UpsertLogsRequest.logs:type_name -> swanlab.terminal.v1.LogRecord - 23, // 7: swanlab.grpc.core.v2.UpsertSavesRequest.saves:type_name -> swanlab.save.v1.SaveRecord - 24, // 8: swanlab.grpc.core.v2.DeliverRunFinishRequest.finish_record:type_name -> swanlab.run.v1.FinishRecord - 25, // 9: swanlab.grpc.core.v2.GetOperationStatsResponse.stats:type_name -> swanlab.operation.v1.OperationStats - 0, // 10: swanlab.grpc.core.v2.CoreService.GetCapabilities:input_type -> swanlab.grpc.core.v2.GetCapabilitiesRequest - 2, // 11: swanlab.grpc.core.v2.CoreService.TeardownService:input_type -> swanlab.grpc.core.v2.TeardownServiceRequest - 4, // 12: swanlab.grpc.core.v2.CoreService.DeliverRunStart:input_type -> swanlab.grpc.core.v2.DeliverRunStartRequest - 6, // 13: swanlab.grpc.core.v2.CoreService.UpsertColumns:input_type -> swanlab.grpc.core.v2.UpsertColumnsRequest - 7, // 14: swanlab.grpc.core.v2.CoreService.UpsertScalars:input_type -> swanlab.grpc.core.v2.UpsertScalarsRequest - 8, // 15: swanlab.grpc.core.v2.CoreService.UpsertMedia:input_type -> swanlab.grpc.core.v2.UpsertMediaRequest - 9, // 16: swanlab.grpc.core.v2.CoreService.UpsertLogs:input_type -> swanlab.grpc.core.v2.UpsertLogsRequest - 10, // 17: swanlab.grpc.core.v2.CoreService.UpsertSaves:input_type -> swanlab.grpc.core.v2.UpsertSavesRequest - 11, // 18: swanlab.grpc.core.v2.CoreService.DeliverRunFinish:input_type -> swanlab.grpc.core.v2.DeliverRunFinishRequest - 13, // 19: swanlab.grpc.core.v2.CoreService.GetOperationStats:input_type -> swanlab.grpc.core.v2.GetOperationStatsRequest - 15, // 20: swanlab.grpc.core.v2.CoreService.ConfirmRunFinish:input_type -> swanlab.grpc.core.v2.ConfirmRunFinishRequest - 1, // 21: swanlab.grpc.core.v2.CoreService.GetCapabilities:output_type -> swanlab.grpc.core.v2.GetCapabilitiesResponse - 3, // 22: swanlab.grpc.core.v2.CoreService.TeardownService:output_type -> swanlab.grpc.core.v2.TeardownServiceResponse - 5, // 23: swanlab.grpc.core.v2.CoreService.DeliverRunStart:output_type -> swanlab.grpc.core.v2.DeliverRunStartResponse - 26, // 24: swanlab.grpc.core.v2.CoreService.UpsertColumns:output_type -> google.protobuf.Empty - 26, // 25: swanlab.grpc.core.v2.CoreService.UpsertScalars:output_type -> google.protobuf.Empty - 26, // 26: swanlab.grpc.core.v2.CoreService.UpsertMedia:output_type -> google.protobuf.Empty - 26, // 27: swanlab.grpc.core.v2.CoreService.UpsertLogs:output_type -> google.protobuf.Empty - 26, // 28: swanlab.grpc.core.v2.CoreService.UpsertSaves:output_type -> google.protobuf.Empty - 12, // 29: swanlab.grpc.core.v2.CoreService.DeliverRunFinish:output_type -> swanlab.grpc.core.v2.DeliverRunFinishResponse - 14, // 30: swanlab.grpc.core.v2.CoreService.GetOperationStats:output_type -> swanlab.grpc.core.v2.GetOperationStatsResponse - 16, // 31: swanlab.grpc.core.v2.CoreService.ConfirmRunFinish:output_type -> swanlab.grpc.core.v2.ConfirmRunFinishResponse - 21, // [21:32] is the sub-list for method output_type - 10, // [10:21] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name -} - -func init() { file_swanlab_grpc_core_v2_core_proto_init() } -func file_swanlab_grpc_core_v2_core_proto_init() { - if File_swanlab_grpc_core_v2_core_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_swanlab_grpc_core_v2_core_proto_rawDesc), len(file_swanlab_grpc_core_v2_core_proto_rawDesc)), - NumEnums: 0, - NumMessages: 17, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_swanlab_grpc_core_v2_core_proto_goTypes, - DependencyIndexes: file_swanlab_grpc_core_v2_core_proto_depIdxs, - MessageInfos: file_swanlab_grpc_core_v2_core_proto_msgTypes, - }.Build() - File_swanlab_grpc_core_v2_core_proto = out.File - file_swanlab_grpc_core_v2_core_proto_goTypes = nil - file_swanlab_grpc_core_v2_core_proto_depIdxs = nil -} diff --git a/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go b/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go deleted file mode 100644 index 58c50e48c..000000000 --- a/core/proto/swanlab/grpc/core/v2/core_grpc.pb.go +++ /dev/null @@ -1,546 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.6.1 -// - protoc v7.35.1 -// source: swanlab/grpc/core/v2/core.proto - -package corev2 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - CoreService_GetCapabilities_FullMethodName = "/swanlab.grpc.core.v2.CoreService/GetCapabilities" - CoreService_TeardownService_FullMethodName = "/swanlab.grpc.core.v2.CoreService/TeardownService" - CoreService_DeliverRunStart_FullMethodName = "/swanlab.grpc.core.v2.CoreService/DeliverRunStart" - CoreService_UpsertColumns_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertColumns" - CoreService_UpsertScalars_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertScalars" - CoreService_UpsertMedia_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertMedia" - CoreService_UpsertLogs_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertLogs" - CoreService_UpsertSaves_FullMethodName = "/swanlab.grpc.core.v2.CoreService/UpsertSaves" - CoreService_DeliverRunFinish_FullMethodName = "/swanlab.grpc.core.v2.CoreService/DeliverRunFinish" - CoreService_GetOperationStats_FullMethodName = "/swanlab.grpc.core.v2.CoreService/GetOperationStats" - CoreService_ConfirmRunFinish_FullMethodName = "/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish" -) - -// CoreServiceClient is the client API for CoreService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 -// -// 相对 v1 的关键变更: -// -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; -// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; -// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 -// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 -type CoreServiceClient interface { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 - TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) - // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 - DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) - // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 - UpsertColumns(ctx context.Context, in *UpsertColumnsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 - UpsertScalars(ctx context.Context, in *UpsertScalarsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 - UpsertMedia(ctx context.Context, in *UpsertMediaRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 - UpsertLogs(ctx context.Context, in *UpsertLogsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 - UpsertSaves(ctx context.Context, in *UpsertSavesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 - DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) - // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 - GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 - ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) -} - -type coreServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient { - return &coreServiceClient{cc} -} - -func (c *coreServiceClient) GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetCapabilitiesResponse) - err := c.cc.Invoke(ctx, CoreService_GetCapabilities_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(TeardownServiceResponse) - err := c.cc.Invoke(ctx, CoreService_TeardownService_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(DeliverRunStartResponse) - err := c.cc.Invoke(ctx, CoreService_DeliverRunStart_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) UpsertColumns(ctx context.Context, in *UpsertColumnsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CoreService_UpsertColumns_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) UpsertScalars(ctx context.Context, in *UpsertScalarsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CoreService_UpsertScalars_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) UpsertMedia(ctx context.Context, in *UpsertMediaRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CoreService_UpsertMedia_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) UpsertLogs(ctx context.Context, in *UpsertLogsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CoreService_UpsertLogs_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) UpsertSaves(ctx context.Context, in *UpsertSavesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CoreService_UpsertSaves_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(DeliverRunFinishResponse) - err := c.cc.Invoke(ctx, CoreService_DeliverRunFinish_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetOperationStatsResponse) - err := c.cc.Invoke(ctx, CoreService_GetOperationStats_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ConfirmRunFinishResponse) - err := c.cc.Invoke(ctx, CoreService_ConfirmRunFinish_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// CoreServiceServer is the server API for CoreService service. -// All implementations must embed UnimplementedCoreServiceServer -// for forward compatibility. -// -// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 -// -// 相对 v1 的关键变更: -// -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; -// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; -// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 -// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 -type CoreServiceServer interface { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 - TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) - // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 - DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) - // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 - UpsertColumns(context.Context, *UpsertColumnsRequest) (*emptypb.Empty, error) - // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 - UpsertScalars(context.Context, *UpsertScalarsRequest) (*emptypb.Empty, error) - // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 - UpsertMedia(context.Context, *UpsertMediaRequest) (*emptypb.Empty, error) - // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 - UpsertLogs(context.Context, *UpsertLogsRequest) (*emptypb.Empty, error) - // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 - UpsertSaves(context.Context, *UpsertSavesRequest) (*emptypb.Empty, error) - // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 - DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) - // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 - GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 - ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) - mustEmbedUnimplementedCoreServiceServer() -} - -// UnimplementedCoreServiceServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedCoreServiceServer struct{} - -func (UnimplementedCoreServiceServer) GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetCapabilities not implemented") -} -func (UnimplementedCoreServiceServer) TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) { - return nil, status.Error(codes.Unimplemented, "method TeardownService not implemented") -} -func (UnimplementedCoreServiceServer) DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) { - return nil, status.Error(codes.Unimplemented, "method DeliverRunStart not implemented") -} -func (UnimplementedCoreServiceServer) UpsertColumns(context.Context, *UpsertColumnsRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpsertColumns not implemented") -} -func (UnimplementedCoreServiceServer) UpsertScalars(context.Context, *UpsertScalarsRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpsertScalars not implemented") -} -func (UnimplementedCoreServiceServer) UpsertMedia(context.Context, *UpsertMediaRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpsertMedia not implemented") -} -func (UnimplementedCoreServiceServer) UpsertLogs(context.Context, *UpsertLogsRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpsertLogs not implemented") -} -func (UnimplementedCoreServiceServer) UpsertSaves(context.Context, *UpsertSavesRequest) (*emptypb.Empty, error) { - return nil, status.Error(codes.Unimplemented, "method UpsertSaves not implemented") -} -func (UnimplementedCoreServiceServer) DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) { - return nil, status.Error(codes.Unimplemented, "method DeliverRunFinish not implemented") -} -func (UnimplementedCoreServiceServer) GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetOperationStats not implemented") -} -func (UnimplementedCoreServiceServer) ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ConfirmRunFinish not implemented") -} -func (UnimplementedCoreServiceServer) mustEmbedUnimplementedCoreServiceServer() {} -func (UnimplementedCoreServiceServer) testEmbeddedByValue() {} - -// UnsafeCoreServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to CoreServiceServer will -// result in compilation errors. -type UnsafeCoreServiceServer interface { - mustEmbedUnimplementedCoreServiceServer() -} - -func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) { - // If the following call panics, it indicates UnimplementedCoreServiceServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&CoreService_ServiceDesc, srv) -} - -func _CoreService_GetCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetCapabilitiesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).GetCapabilities(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_GetCapabilities_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).GetCapabilities(ctx, req.(*GetCapabilitiesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_TeardownService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TeardownServiceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).TeardownService(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_TeardownService_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).TeardownService(ctx, req.(*TeardownServiceRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_DeliverRunStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeliverRunStartRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).DeliverRunStart(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_DeliverRunStart_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).DeliverRunStart(ctx, req.(*DeliverRunStartRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_UpsertColumns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertColumnsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).UpsertColumns(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_UpsertColumns_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpsertColumns(ctx, req.(*UpsertColumnsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_UpsertScalars_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertScalarsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).UpsertScalars(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_UpsertScalars_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpsertScalars(ctx, req.(*UpsertScalarsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_UpsertMedia_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertMediaRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).UpsertMedia(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_UpsertMedia_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpsertMedia(ctx, req.(*UpsertMediaRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_UpsertLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertLogsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).UpsertLogs(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_UpsertLogs_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpsertLogs(ctx, req.(*UpsertLogsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_UpsertSaves_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertSavesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).UpsertSaves(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_UpsertSaves_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).UpsertSaves(ctx, req.(*UpsertSavesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_DeliverRunFinish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeliverRunFinishRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).DeliverRunFinish(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_DeliverRunFinish_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).DeliverRunFinish(ctx, req.(*DeliverRunFinishRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_GetOperationStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetOperationStatsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).GetOperationStats(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_GetOperationStats_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).GetOperationStats(ctx, req.(*GetOperationStatsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _CoreService_ConfirmRunFinish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ConfirmRunFinishRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServiceServer).ConfirmRunFinish(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CoreService_ConfirmRunFinish_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).ConfirmRunFinish(ctx, req.(*ConfirmRunFinishRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// CoreService_ServiceDesc is the grpc.ServiceDesc for CoreService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var CoreService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "swanlab.grpc.core.v2.CoreService", - HandlerType: (*CoreServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetCapabilities", - Handler: _CoreService_GetCapabilities_Handler, - }, - { - MethodName: "TeardownService", - Handler: _CoreService_TeardownService_Handler, - }, - { - MethodName: "DeliverRunStart", - Handler: _CoreService_DeliverRunStart_Handler, - }, - { - MethodName: "UpsertColumns", - Handler: _CoreService_UpsertColumns_Handler, - }, - { - MethodName: "UpsertScalars", - Handler: _CoreService_UpsertScalars_Handler, - }, - { - MethodName: "UpsertMedia", - Handler: _CoreService_UpsertMedia_Handler, - }, - { - MethodName: "UpsertLogs", - Handler: _CoreService_UpsertLogs_Handler, - }, - { - MethodName: "UpsertSaves", - Handler: _CoreService_UpsertSaves_Handler, - }, - { - MethodName: "DeliverRunFinish", - Handler: _CoreService_DeliverRunFinish_Handler, - }, - { - MethodName: "GetOperationStats", - Handler: _CoreService_GetOperationStats_Handler, - }, - { - MethodName: "ConfirmRunFinish", - Handler: _CoreService_ConfirmRunFinish_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "swanlab/grpc/core/v2/core.proto", -} diff --git a/protos/swanlab/grpc/core/v1/core.proto b/protos/swanlab/grpc/core/v1/core.proto index bb5ababb9..76538f5d1 100644 --- a/protos/swanlab/grpc/core/v1/core.proto +++ b/protos/swanlab/grpc/core/v1/core.proto @@ -13,8 +13,22 @@ import "swanlab/settings/core/v1/core.proto"; option go_package = "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1"; -// CoreService 是核心业务接口,用于同步或异步地接收实验记录 +// CoreService 是核心业务接口,用于同步或异步地接收实验记录。 +// +// 生命周期约定: +// +// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; +// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 service CoreService { + // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); + + // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + rpc TeardownService(TeardownServiceRequest) returns (TeardownServiceResponse); + // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 rpc DeliverRunStart(DeliverRunStartRequest) returns (DeliverRunStartResponse); @@ -36,11 +50,38 @@ service CoreService { // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 rpc DeliverRunFinish(DeliverRunFinishRequest) returns (DeliverRunFinishResponse); - // GetOperationStats 返回 Core 当前运行状态和上传进度快照。 - rpc GetOperationStats(google.protobuf.Empty) returns (GetOperationStatsResponse); + // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 + rpc GetOperationStats(GetOperationStatsRequest) returns (GetOperationStatsResponse); + + // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + rpc ConfirmRunFinish(ConfirmRunFinishRequest) returns (ConfirmRunFinishResponse); +} + +// GetCapabilities 请求。当前无字段,预留后续扩展。 +message GetCapabilitiesRequest { +} + +// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, +// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 +message GetCapabilitiesResponse { + string protocol_version = 1; // 协议版本,与 Python SDK 期望版本锁步 + string core_version = 2; // core 二进制编译版本(构建管线注入) + bool lifecycle = 3; // 生命周期与进程管理能力 + repeated string supported_modes = 4; // 支持的 run 模式,如 online/local/offline + bool store = 5; // 本地持久化能力 + bool transport = 6; // 云端上传能力 + bool payload = 7; // 内联 payload 能力(online skip_store 模式) + bool sync = 8; // 离线数据同步能力 + int64 max_receive_message_bytes = 9; // 服务端单条消息接收上限,用于客户端协商 +} + +// TeardownService 请求。 +message TeardownServiceRequest { + string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 +} - // ConfirmRunFinish 确认 Core 运行结束,可以安全退出 - rpc ConfirmRunFinish(google.protobuf.Empty) returns (ConfirmRunFinishResponse); +// TeardownService 响应。 +message TeardownServiceResponse { } // DeliverRunStart 请求,生命周期函数,运行正式开始 @@ -51,44 +92,51 @@ message DeliverRunStartRequest { // DeliverRunStart 响应 message DeliverRunStartResponse { - bool success = 1; // 请求是否成功 - string message = 2; // 请求失败的响应 - swanlab.run.v1.StartRecord run = 3; // 最终创建的 Run 记录 - string path = 4; // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) - string name = 5; // 对应的实验名称 - int64 global_step = 6; // 起始全局步数 - int64 global_system_step = 7; // 起始全局系统步数 - bool new_experiment = 8; // 是否是新实验 + bool success = 1; // 请求是否成功 + string message = 2; // 请求失败的响应 + swanlab.run.v1.StartRecord run = 3; // 最终创建的 Run 记录 + string path = 4; // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) + string name = 5; // 对应的实验名称 + int64 global_step = 6; // 起始全局步数 + int64 global_system_step = 7; // 起始全局系统步数 + bool new_experiment = 8; // 是否是新实验 + string run_handle = 9; // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 } // UpsertColumns 请求,异步插入一批列定义 message UpsertColumnsRequest { repeated swanlab.metric.column.v1.ColumnRecord columns = 1; // 列定义列表 + string run_handle = 2; // 目标 run 会话标识 } // UpsertScalars 请求,异步插入一批标量数据 message UpsertScalarsRequest { repeated swanlab.metric.data.v1.ScalarRecord data = 1; // 标量记录列表 + string run_handle = 2; // 目标 run 会话标识 } // UpsertMedia 请求,异步插入一批媒体数据 message UpsertMediaRequest { repeated swanlab.metric.data.v1.MediaRecord data = 1; // 媒体记录列表 + string run_handle = 2; // 目标 run 会话标识 } // UpsertLogs 请求,异步插入一批终端输出 message UpsertLogsRequest { repeated swanlab.terminal.v1.LogRecord logs = 1; // 终端日志记录列表 + string run_handle = 2; // 目标 run 会话标识 } // UpsertSaves 请求,异步插入一批文件保存记录 message UpsertSavesRequest { repeated swanlab.save.v1.SaveRecord saves = 1; // 保存记录列表 + string run_handle = 2; // 目标 run 会话标识 } // DeliverRunFinish 请求,生命周期函数,运行结束 message DeliverRunFinishRequest { swanlab.run.v1.FinishRecord finish_record = 1; // Run 结束记录 + string run_handle = 2; // 目标 run 会话标识 } // DeliverRunFinish 响应 @@ -97,6 +145,11 @@ message DeliverRunFinishResponse { string message = 2; // 请求失败的响应 } +// GetOperationStats 请求。 +message GetOperationStatsRequest { + string run_handle = 1; // 目标 run 会话标识 +} + // GetOperationStats 相应 message GetOperationStatsResponse { bool success = 1; // 请求是否成功 @@ -104,6 +157,11 @@ message GetOperationStatsResponse { swanlab.operation.v1.OperationStats stats = 3; // Core 当前运行状态和上传进度快照 } +// ConfirmRunFinish 请求。 +message ConfirmRunFinishRequest { + string run_handle = 1; // 目标 run 会话标识 +} + // ConfirmRunFinish 响应 message ConfirmRunFinishResponse { bool success = 1; // 请求是否成功 diff --git a/protos/swanlab/grpc/core/v2/core.proto b/protos/swanlab/grpc/core/v2/core.proto deleted file mode 100644 index 50a2e5b4b..000000000 --- a/protos/swanlab/grpc/core/v2/core.proto +++ /dev/null @@ -1,170 +0,0 @@ -syntax = "proto3"; - -package swanlab.grpc.core.v2; - -import "google/protobuf/empty.proto"; -import "swanlab/run/v1/run.proto"; -import "swanlab/metric/data/v1/data.proto"; -import "swanlab/metric/column/v1/column.proto"; -import "swanlab/terminal/v1/log.proto"; -import "swanlab/save/v1/save.proto"; -import "swanlab/operation/v1/operation.proto"; -import "swanlab/settings/core/v1/core.proto"; - -option go_package = "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2"; - -// CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 -// -// 相对 v1 的关键变更: -// -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; -// 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; -// 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 -// v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 -service CoreService { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); - - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 - rpc TeardownService(TeardownServiceRequest) returns (TeardownServiceResponse); - - // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 - rpc DeliverRunStart(DeliverRunStartRequest) returns (DeliverRunStartResponse); - - // UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 - rpc UpsertColumns(UpsertColumnsRequest) returns (google.protobuf.Empty); - - // UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 - rpc UpsertScalars(UpsertScalarsRequest) returns (google.protobuf.Empty); - - // UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 - rpc UpsertMedia(UpsertMediaRequest) returns (google.protobuf.Empty); - - // UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 - rpc UpsertLogs(UpsertLogsRequest) returns (google.protobuf.Empty); - - // UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 - rpc UpsertSaves(UpsertSavesRequest) returns (google.protobuf.Empty); - - // DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 - rpc DeliverRunFinish(DeliverRunFinishRequest) returns (DeliverRunFinishResponse); - - // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 - rpc GetOperationStats(GetOperationStatsRequest) returns (GetOperationStatsResponse); - - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 - rpc ConfirmRunFinish(ConfirmRunFinishRequest) returns (ConfirmRunFinishResponse); -} - -// GetCapabilities 请求。当前无字段,预留后续扩展。 -message GetCapabilitiesRequest { -} - -// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, -// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 -message GetCapabilitiesResponse { - string protocol_version = 1; // 协议版本,与 Python SDK 期望版本锁步 - string core_version = 2; // core 二进制编译版本(构建管线注入) - bool lifecycle = 3; // 生命周期与进程管理能力 - repeated string supported_modes = 4; // 支持的 run 模式,如 online/local/offline - bool store = 5; // 本地持久化能力 - bool transport = 6; // 云端上传能力 - bool payload = 7; // 内联 payload 能力(online skip_store 模式) - bool sync = 8; // 离线数据同步能力 - int64 max_receive_message_bytes = 9; // 服务端单条消息接收上限,用于客户端协商 -} - -// TeardownService 请求。 -message TeardownServiceRequest { - string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 -} - -// TeardownService 响应。 -message TeardownServiceResponse { -} - -// DeliverRunStart 请求,生命周期函数,运行正式开始 -message DeliverRunStartRequest { - swanlab.settings.core.v1.CoreSettings core_settings = 1; // Run 服务配置 - swanlab.run.v1.StartRecord start_record = 2; // Run 创建记录 -} - -// DeliverRunStart 响应 -message DeliverRunStartResponse { - bool success = 1; // 请求是否成功 - string message = 2; // 请求失败的响应 - swanlab.run.v1.StartRecord run = 3; // 最终创建的 Run 记录 - string path = 4; // 对应的实验路径,格式为 /:username/:project_name/:slug(run_id) - string name = 5; // 对应的实验名称 - int64 global_step = 6; // 起始全局步数 - int64 global_system_step = 7; // 起始全局系统步数 - bool new_experiment = 8; // 是否是新实验 - string run_handle = 9; // 服务端生成的 opaque 会话标识,后续所有 run 级 RPC 必须携带 -} - -// UpsertColumns 请求,异步插入一批列定义 -message UpsertColumnsRequest { - repeated swanlab.metric.column.v1.ColumnRecord columns = 1; // 列定义列表 - string run_handle = 2; // 目标 run 会话标识 -} - -// UpsertScalars 请求,异步插入一批标量数据 -message UpsertScalarsRequest { - repeated swanlab.metric.data.v1.ScalarRecord data = 1; // 标量记录列表 - string run_handle = 2; // 目标 run 会话标识 -} - -// UpsertMedia 请求,异步插入一批媒体数据 -message UpsertMediaRequest { - repeated swanlab.metric.data.v1.MediaRecord data = 1; // 媒体记录列表 - string run_handle = 2; // 目标 run 会话标识 -} - -// UpsertLogs 请求,异步插入一批终端输出 -message UpsertLogsRequest { - repeated swanlab.terminal.v1.LogRecord logs = 1; // 终端日志记录列表 - string run_handle = 2; // 目标 run 会话标识 -} - -// UpsertSaves 请求,异步插入一批文件保存记录 -message UpsertSavesRequest { - repeated swanlab.save.v1.SaveRecord saves = 1; // 保存记录列表 - string run_handle = 2; // 目标 run 会话标识 -} - -// DeliverRunFinish 请求,生命周期函数,运行结束 -message DeliverRunFinishRequest { - swanlab.run.v1.FinishRecord finish_record = 1; // Run 结束记录 - string run_handle = 2; // 目标 run 会话标识 -} - -// DeliverRunFinish 响应 -message DeliverRunFinishResponse { - bool success = 1; // 请求是否成功 - string message = 2; // 请求失败的响应 -} - -// GetOperationStats 请求。 -message GetOperationStatsRequest { - string run_handle = 1; // 目标 run 会话标识 -} - -// GetOperationStats 响应 -message GetOperationStatsResponse { - bool success = 1; // 请求是否成功 - string message = 2; // 请求失败的响应 - swanlab.operation.v1.OperationStats stats = 3; // Core 当前运行状态和上传进度快照 -} - -// ConfirmRunFinish 请求。 -message ConfirmRunFinishRequest { - string run_handle = 1; // 目标 run 会话标识 -} - -// ConfirmRunFinish 响应 -message ConfirmRunFinishResponse { - bool success = 1; // 请求是否成功 - string message = 2; // 请求失败的响应 -} diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py index 008916c96..74d176ffd 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py @@ -32,7 +32,7 @@ from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as swanlab_dot_settings_dot_core_dot_v1_dot_core__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xca\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\"O\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\"J\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\"G\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\"A\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\"@\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\"N\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xc5\x06\n\x0b\x43oreService\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12\\\n\x11GetOperationStats\x12\x16.google.protobuf.Empty\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12Z\n\x10\x43onfirmRunFinish\x12\x16.google.protobuf.Empty\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n\x16GetCapabilitiesRequest\"\xd9\x01\n\x17GetCapabilitiesResponse\x12\x18\n\x10protocol_version\x18\x01 \x01(\t\x12\x14\n\x0c\x63ore_version\x18\x02 \x01(\t\x12\x11\n\tlifecycle\x18\x03 \x01(\x08\x12\x17\n\x0fsupported_modes\x18\x04 \x03(\t\x12\r\n\x05store\x18\x05 \x01(\x08\x12\x11\n\ttransport\x18\x06 \x01(\x08\x12\x0f\n\x07payload\x18\x07 \x01(\x08\x12\x0c\n\x04sync\x18\x08 \x01(\x08\x12!\n\x19max_receive_message_bytes\x18\t \x01(\x03\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xd4\x08\n\x0b\x43oreService\x12n\n\x0fGetCapabilities\x12,.swanlab.grpc.core.v1.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v1.GetCapabilitiesResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -40,28 +40,40 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'ZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1' - _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=321 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=459 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=462 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=664 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=666 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=745 - _globals['_UPSERTSCALARSREQUEST']._serialized_start=747 - _globals['_UPSERTSCALARSREQUEST']._serialized_end=821 - _globals['_UPSERTMEDIAREQUEST']._serialized_start=823 - _globals['_UPSERTMEDIAREQUEST']._serialized_end=894 - _globals['_UPSERTLOGSREQUEST']._serialized_start=896 - _globals['_UPSERTLOGSREQUEST']._serialized_end=961 - _globals['_UPSERTSAVESREQUEST']._serialized_start=963 - _globals['_UPSERTSAVESREQUEST']._serialized_end=1027 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1029 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1107 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1109 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1169 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1171 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1285 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1287 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1347 - _globals['_CORESERVICE']._serialized_start=1350 - _globals['_CORESERVICE']._serialized_end=2187 + _globals['_GETCAPABILITIESREQUEST']._serialized_start=320 + _globals['_GETCAPABILITIESREQUEST']._serialized_end=344 + _globals['_GETCAPABILITIESRESPONSE']._serialized_start=347 + _globals['_GETCAPABILITIESRESPONSE']._serialized_end=564 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=566 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=611 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=613 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=638 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=641 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=779 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=782 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=1004 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=1006 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=1105 + _globals['_UPSERTSCALARSREQUEST']._serialized_start=1107 + _globals['_UPSERTSCALARSREQUEST']._serialized_end=1201 + _globals['_UPSERTMEDIAREQUEST']._serialized_start=1203 + _globals['_UPSERTMEDIAREQUEST']._serialized_end=1294 + _globals['_UPSERTLOGSREQUEST']._serialized_start=1296 + _globals['_UPSERTLOGSREQUEST']._serialized_end=1381 + _globals['_UPSERTSAVESREQUEST']._serialized_start=1383 + _globals['_UPSERTSAVESREQUEST']._serialized_end=1467 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1469 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1567 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1569 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1629 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1631 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1677 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1679 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1793 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1795 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1840 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1842 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1902 + _globals['_CORESERVICE']._serialized_start=1905 + _globals['_CORESERVICE']._serialized_end=3013 # @@protoc_insertion_point(module_scope) diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi index 3a95628a4..7e4ca87c6 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi @@ -14,6 +14,42 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor +class GetCapabilitiesRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCapabilitiesResponse(_message.Message): + __slots__ = ("protocol_version", "core_version", "lifecycle", "supported_modes", "store", "transport", "payload", "sync", "max_receive_message_bytes") + PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int] + CORE_VERSION_FIELD_NUMBER: _ClassVar[int] + LIFECYCLE_FIELD_NUMBER: _ClassVar[int] + SUPPORTED_MODES_FIELD_NUMBER: _ClassVar[int] + STORE_FIELD_NUMBER: _ClassVar[int] + TRANSPORT_FIELD_NUMBER: _ClassVar[int] + PAYLOAD_FIELD_NUMBER: _ClassVar[int] + SYNC_FIELD_NUMBER: _ClassVar[int] + MAX_RECEIVE_MESSAGE_BYTES_FIELD_NUMBER: _ClassVar[int] + protocol_version: str + core_version: str + lifecycle: bool + supported_modes: _containers.RepeatedScalarFieldContainer[str] + store: bool + transport: bool + payload: bool + sync: bool + max_receive_message_bytes: int + def __init__(self, protocol_version: _Optional[str] = ..., core_version: _Optional[str] = ..., lifecycle: bool = ..., supported_modes: _Optional[_Iterable[str]] = ..., store: bool = ..., transport: bool = ..., payload: bool = ..., sync: bool = ..., max_receive_message_bytes: _Optional[int] = ...) -> None: ... + +class TeardownServiceRequest(_message.Message): + __slots__ = ("owner_token",) + OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] + owner_token: str + def __init__(self, owner_token: _Optional[str] = ...) -> None: ... + +class TeardownServiceResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class DeliverRunStartRequest(_message.Message): __slots__ = ("core_settings", "start_record") CORE_SETTINGS_FIELD_NUMBER: _ClassVar[int] @@ -23,7 +59,7 @@ class DeliverRunStartRequest(_message.Message): def __init__(self, core_settings: _Optional[_Union[_core_pb2.CoreSettings, _Mapping]] = ..., start_record: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ...) -> None: ... class DeliverRunStartResponse(_message.Message): - __slots__ = ("success", "message", "run", "path", "name", "global_step", "global_system_step", "new_experiment") + __slots__ = ("success", "message", "run", "path", "name", "global_step", "global_system_step", "new_experiment", "run_handle") SUCCESS_FIELD_NUMBER: _ClassVar[int] MESSAGE_FIELD_NUMBER: _ClassVar[int] RUN_FIELD_NUMBER: _ClassVar[int] @@ -32,6 +68,7 @@ class DeliverRunStartResponse(_message.Message): GLOBAL_STEP_FIELD_NUMBER: _ClassVar[int] GLOBAL_SYSTEM_STEP_FIELD_NUMBER: _ClassVar[int] NEW_EXPERIMENT_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] success: bool message: str run: _run_pb2.StartRecord @@ -40,43 +77,56 @@ class DeliverRunStartResponse(_message.Message): global_step: int global_system_step: int new_experiment: bool - def __init__(self, success: bool = ..., message: _Optional[str] = ..., run: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ..., path: _Optional[str] = ..., name: _Optional[str] = ..., global_step: _Optional[int] = ..., global_system_step: _Optional[int] = ..., new_experiment: bool = ...) -> None: ... + run_handle: str + def __init__(self, success: bool = ..., message: _Optional[str] = ..., run: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ..., path: _Optional[str] = ..., name: _Optional[str] = ..., global_step: _Optional[int] = ..., global_system_step: _Optional[int] = ..., new_experiment: bool = ..., run_handle: _Optional[str] = ...) -> None: ... class UpsertColumnsRequest(_message.Message): - __slots__ = ("columns",) + __slots__ = ("columns", "run_handle") COLUMNS_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] columns: _containers.RepeatedCompositeFieldContainer[_column_pb2.ColumnRecord] - def __init__(self, columns: _Optional[_Iterable[_Union[_column_pb2.ColumnRecord, _Mapping]]] = ...) -> None: ... + run_handle: str + def __init__(self, columns: _Optional[_Iterable[_Union[_column_pb2.ColumnRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... class UpsertScalarsRequest(_message.Message): - __slots__ = ("data",) + __slots__ = ("data", "run_handle") DATA_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] data: _containers.RepeatedCompositeFieldContainer[_data_pb2.ScalarRecord] - def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.ScalarRecord, _Mapping]]] = ...) -> None: ... + run_handle: str + def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.ScalarRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... class UpsertMediaRequest(_message.Message): - __slots__ = ("data",) + __slots__ = ("data", "run_handle") DATA_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] data: _containers.RepeatedCompositeFieldContainer[_data_pb2.MediaRecord] - def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.MediaRecord, _Mapping]]] = ...) -> None: ... + run_handle: str + def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.MediaRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... class UpsertLogsRequest(_message.Message): - __slots__ = ("logs",) + __slots__ = ("logs", "run_handle") LOGS_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] logs: _containers.RepeatedCompositeFieldContainer[_log_pb2.LogRecord] - def __init__(self, logs: _Optional[_Iterable[_Union[_log_pb2.LogRecord, _Mapping]]] = ...) -> None: ... + run_handle: str + def __init__(self, logs: _Optional[_Iterable[_Union[_log_pb2.LogRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... class UpsertSavesRequest(_message.Message): - __slots__ = ("saves",) + __slots__ = ("saves", "run_handle") SAVES_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] saves: _containers.RepeatedCompositeFieldContainer[_save_pb2.SaveRecord] - def __init__(self, saves: _Optional[_Iterable[_Union[_save_pb2.SaveRecord, _Mapping]]] = ...) -> None: ... + run_handle: str + def __init__(self, saves: _Optional[_Iterable[_Union[_save_pb2.SaveRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... class DeliverRunFinishRequest(_message.Message): - __slots__ = ("finish_record",) + __slots__ = ("finish_record", "run_handle") FINISH_RECORD_FIELD_NUMBER: _ClassVar[int] + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] finish_record: _run_pb2.FinishRecord - def __init__(self, finish_record: _Optional[_Union[_run_pb2.FinishRecord, _Mapping]] = ...) -> None: ... + run_handle: str + def __init__(self, finish_record: _Optional[_Union[_run_pb2.FinishRecord, _Mapping]] = ..., run_handle: _Optional[str] = ...) -> None: ... class DeliverRunFinishResponse(_message.Message): __slots__ = ("success", "message") @@ -86,6 +136,12 @@ class DeliverRunFinishResponse(_message.Message): message: str def __init__(self, success: bool = ..., message: _Optional[str] = ...) -> None: ... +class GetOperationStatsRequest(_message.Message): + __slots__ = ("run_handle",) + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + run_handle: str + def __init__(self, run_handle: _Optional[str] = ...) -> None: ... + class GetOperationStatsResponse(_message.Message): __slots__ = ("success", "message", "stats") SUCCESS_FIELD_NUMBER: _ClassVar[int] @@ -96,6 +152,12 @@ class GetOperationStatsResponse(_message.Message): stats: _operation_pb2.OperationStats def __init__(self, success: bool = ..., message: _Optional[str] = ..., stats: _Optional[_Union[_operation_pb2.OperationStats, _Mapping]] = ...) -> None: ... +class ConfirmRunFinishRequest(_message.Message): + __slots__ = ("run_handle",) + RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] + run_handle: str + def __init__(self, run_handle: _Optional[str] = ...) -> None: ... + class ConfirmRunFinishResponse(_message.Message): __slots__ = ("success", "message") SUCCESS_FIELD_NUMBER: _ClassVar[int] diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py index 2e93153ac..3116c4712 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py @@ -27,7 +27,14 @@ class CoreServiceStub(object): - """CoreService 是核心业务接口,用于同步或异步地接收实验记录 + """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 + + 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ def __init__(self, channel): @@ -36,6 +43,16 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ + self.GetCapabilities = channel.unary_unary( + '/swanlab.grpc.core.v1.CoreService/GetCapabilities', + request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.FromString, + _registered_method=True) + self.TeardownService = channel.unary_unary( + '/swanlab.grpc.core.v1.CoreService/TeardownService', + request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceResponse.FromString, + _registered_method=True) self.DeliverRunStart = channel.unary_unary( '/swanlab.grpc.core.v1.CoreService/DeliverRunStart', request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.DeliverRunStartRequest.SerializeToString, @@ -73,20 +90,42 @@ def __init__(self, channel): _registered_method=True) self.GetOperationStats = channel.unary_unary( '/swanlab.grpc.core.v1.CoreService/GetOperationStats', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsRequest.SerializeToString, response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsResponse.FromString, _registered_method=True) self.ConfirmRunFinish = channel.unary_unary( '/swanlab.grpc.core.v1.CoreService/ConfirmRunFinish', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishResponse.FromString, _registered_method=True) class CoreServiceServicer(object): - """CoreService 是核心业务接口,用于同步或异步地接收实验记录 + """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 + + 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ + def GetCapabilities(self, request, context): + """GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def TeardownService(self, request, context): + """TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, + 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def DeliverRunStart(self, request, context): """DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 """ @@ -137,14 +176,14 @@ def DeliverRunFinish(self, request, context): raise NotImplementedError('Method not implemented!') def GetOperationStats(self, request, context): - """GetOperationStats 返回 Core 当前运行状态和上传进度快照。 + """GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def ConfirmRunFinish(self, request, context): - """ConfirmRunFinish 确认 Core 运行结束,可以安全退出 + """ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -153,6 +192,16 @@ def ConfirmRunFinish(self, request, context): def add_CoreServiceServicer_to_server(servicer, server): rpc_method_handlers = { + 'GetCapabilities': grpc.unary_unary_rpc_method_handler( + servicer.GetCapabilities, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.SerializeToString, + ), + 'TeardownService': grpc.unary_unary_rpc_method_handler( + servicer.TeardownService, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceResponse.SerializeToString, + ), 'DeliverRunStart': grpc.unary_unary_rpc_method_handler( servicer.DeliverRunStart, request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.DeliverRunStartRequest.FromString, @@ -190,12 +239,12 @@ def add_CoreServiceServicer_to_server(servicer, server): ), 'GetOperationStats': grpc.unary_unary_rpc_method_handler( servicer.GetOperationStats, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsRequest.FromString, response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsResponse.SerializeToString, ), 'ConfirmRunFinish': grpc.unary_unary_rpc_method_handler( servicer.ConfirmRunFinish, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishRequest.FromString, response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishResponse.SerializeToString, ), } @@ -207,9 +256,70 @@ def add_CoreServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class CoreService(object): - """CoreService 是核心业务接口,用于同步或异步地接收实验记录 + """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 + + 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + + 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; + 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ + @staticmethod + def GetCapabilities(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v1.CoreService/GetCapabilities', + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def TeardownService(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/swanlab.grpc.core.v1.CoreService/TeardownService', + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.TeardownServiceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def DeliverRunStart(request, target, @@ -414,7 +524,7 @@ def GetOperationStats(request, request, target, '/swanlab.grpc.core.v1.CoreService/GetOperationStats', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsRequest.SerializeToString, swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetOperationStatsResponse.FromString, options, channel_credentials, @@ -441,7 +551,7 @@ def ConfirmRunFinish(request, request, target, '/swanlab.grpc.core.v1.CoreService/ConfirmRunFinish', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.ConfirmRunFinishResponse.FromString, options, channel_credentials, diff --git a/swanlab/proto/swanlab/grpc/core/v2/__init__.py b/swanlab/proto/swanlab/grpc/core/v2/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py deleted file mode 100644 index 4aa17082a..000000000 --- a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: swanlab/grpc/core/v2/core.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'swanlab/grpc/core/v2/core.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from swanlab.proto.swanlab.run.v1 import run_pb2 as swanlab_dot_run_dot_v1_dot_run__pb2 -from swanlab.proto.swanlab.metric.data.v1 import data_pb2 as swanlab_dot_metric_dot_data_dot_v1_dot_data__pb2 -from swanlab.proto.swanlab.metric.column.v1 import column_pb2 as swanlab_dot_metric_dot_column_dot_v1_dot_column__pb2 -from swanlab.proto.swanlab.terminal.v1 import log_pb2 as swanlab_dot_terminal_dot_v1_dot_log__pb2 -from swanlab.proto.swanlab.save.v1 import save_pb2 as swanlab_dot_save_dot_v1_dot_save__pb2 -from swanlab.proto.swanlab.operation.v1 import operation_pb2 as swanlab_dot_operation_dot_v1_dot_operation__pb2 -from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as swanlab_dot_settings_dot_core_dot_v1_dot_core__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v2/core.proto\x12\x14swanlab.grpc.core.v2\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n\x16GetCapabilitiesRequest\"\xd9\x01\n\x17GetCapabilitiesResponse\x12\x18\n\x10protocol_version\x18\x01 \x01(\t\x12\x14\n\x0c\x63ore_version\x18\x02 \x01(\t\x12\x11\n\tlifecycle\x18\x03 \x01(\x08\x12\x17\n\x0fsupported_modes\x18\x04 \x03(\t\x12\r\n\x05store\x18\x05 \x01(\x08\x12\x11\n\ttransport\x18\x06 \x01(\x08\x12\x0f\n\x07payload\x18\x07 \x01(\x08\x12\x0c\n\x04sync\x18\x08 \x01(\x08\x12!\n\x19max_receive_message_bytes\x18\t \x01(\x03\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xd4\x08\n\x0b\x43oreService\x12n\n\x0fGetCapabilities\x12,.swanlab.grpc.core.v2.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v2.GetCapabilitiesResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v2.TeardownServiceRequest\x1a-.swanlab.grpc.core.v2.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v2.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v2.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v2.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v2.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v2.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v2.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v2.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v2.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v2.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v2.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v2.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v2.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v2.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'swanlab.grpc.core.v2.core_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'ZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v2;corev2' - _globals['_GETCAPABILITIESREQUEST']._serialized_start=320 - _globals['_GETCAPABILITIESREQUEST']._serialized_end=344 - _globals['_GETCAPABILITIESRESPONSE']._serialized_start=347 - _globals['_GETCAPABILITIESRESPONSE']._serialized_end=564 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=566 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=611 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=613 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=638 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=641 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=779 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=782 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=1004 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=1006 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=1105 - _globals['_UPSERTSCALARSREQUEST']._serialized_start=1107 - _globals['_UPSERTSCALARSREQUEST']._serialized_end=1201 - _globals['_UPSERTMEDIAREQUEST']._serialized_start=1203 - _globals['_UPSERTMEDIAREQUEST']._serialized_end=1294 - _globals['_UPSERTLOGSREQUEST']._serialized_start=1296 - _globals['_UPSERTLOGSREQUEST']._serialized_end=1381 - _globals['_UPSERTSAVESREQUEST']._serialized_start=1383 - _globals['_UPSERTSAVESREQUEST']._serialized_end=1467 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1469 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1567 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1569 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1629 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1631 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1677 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1679 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1793 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1795 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1840 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1842 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1902 - _globals['_CORESERVICE']._serialized_start=1905 - _globals['_CORESERVICE']._serialized_end=3013 -# @@protoc_insertion_point(module_scope) diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi b/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi deleted file mode 100644 index 7e4ca87c6..000000000 --- a/swanlab/proto/swanlab/grpc/core/v2/core_pb2.pyi +++ /dev/null @@ -1,167 +0,0 @@ -from google.protobuf import empty_pb2 as _empty_pb2 -from swanlab.proto.swanlab.run.v1 import run_pb2 as _run_pb2 -from swanlab.proto.swanlab.metric.data.v1 import data_pb2 as _data_pb2 -from swanlab.proto.swanlab.metric.column.v1 import column_pb2 as _column_pb2 -from swanlab.proto.swanlab.terminal.v1 import log_pb2 as _log_pb2 -from swanlab.proto.swanlab.save.v1 import save_pb2 as _save_pb2 -from swanlab.proto.swanlab.operation.v1 import operation_pb2 as _operation_pb2 -from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as _core_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class GetCapabilitiesRequest(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class GetCapabilitiesResponse(_message.Message): - __slots__ = ("protocol_version", "core_version", "lifecycle", "supported_modes", "store", "transport", "payload", "sync", "max_receive_message_bytes") - PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int] - CORE_VERSION_FIELD_NUMBER: _ClassVar[int] - LIFECYCLE_FIELD_NUMBER: _ClassVar[int] - SUPPORTED_MODES_FIELD_NUMBER: _ClassVar[int] - STORE_FIELD_NUMBER: _ClassVar[int] - TRANSPORT_FIELD_NUMBER: _ClassVar[int] - PAYLOAD_FIELD_NUMBER: _ClassVar[int] - SYNC_FIELD_NUMBER: _ClassVar[int] - MAX_RECEIVE_MESSAGE_BYTES_FIELD_NUMBER: _ClassVar[int] - protocol_version: str - core_version: str - lifecycle: bool - supported_modes: _containers.RepeatedScalarFieldContainer[str] - store: bool - transport: bool - payload: bool - sync: bool - max_receive_message_bytes: int - def __init__(self, protocol_version: _Optional[str] = ..., core_version: _Optional[str] = ..., lifecycle: bool = ..., supported_modes: _Optional[_Iterable[str]] = ..., store: bool = ..., transport: bool = ..., payload: bool = ..., sync: bool = ..., max_receive_message_bytes: _Optional[int] = ...) -> None: ... - -class TeardownServiceRequest(_message.Message): - __slots__ = ("owner_token",) - OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] - owner_token: str - def __init__(self, owner_token: _Optional[str] = ...) -> None: ... - -class TeardownServiceResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class DeliverRunStartRequest(_message.Message): - __slots__ = ("core_settings", "start_record") - CORE_SETTINGS_FIELD_NUMBER: _ClassVar[int] - START_RECORD_FIELD_NUMBER: _ClassVar[int] - core_settings: _core_pb2.CoreSettings - start_record: _run_pb2.StartRecord - def __init__(self, core_settings: _Optional[_Union[_core_pb2.CoreSettings, _Mapping]] = ..., start_record: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ...) -> None: ... - -class DeliverRunStartResponse(_message.Message): - __slots__ = ("success", "message", "run", "path", "name", "global_step", "global_system_step", "new_experiment", "run_handle") - SUCCESS_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FIELD_NUMBER: _ClassVar[int] - RUN_FIELD_NUMBER: _ClassVar[int] - PATH_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - GLOBAL_STEP_FIELD_NUMBER: _ClassVar[int] - GLOBAL_SYSTEM_STEP_FIELD_NUMBER: _ClassVar[int] - NEW_EXPERIMENT_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - success: bool - message: str - run: _run_pb2.StartRecord - path: str - name: str - global_step: int - global_system_step: int - new_experiment: bool - run_handle: str - def __init__(self, success: bool = ..., message: _Optional[str] = ..., run: _Optional[_Union[_run_pb2.StartRecord, _Mapping]] = ..., path: _Optional[str] = ..., name: _Optional[str] = ..., global_step: _Optional[int] = ..., global_system_step: _Optional[int] = ..., new_experiment: bool = ..., run_handle: _Optional[str] = ...) -> None: ... - -class UpsertColumnsRequest(_message.Message): - __slots__ = ("columns", "run_handle") - COLUMNS_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - columns: _containers.RepeatedCompositeFieldContainer[_column_pb2.ColumnRecord] - run_handle: str - def __init__(self, columns: _Optional[_Iterable[_Union[_column_pb2.ColumnRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class UpsertScalarsRequest(_message.Message): - __slots__ = ("data", "run_handle") - DATA_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - data: _containers.RepeatedCompositeFieldContainer[_data_pb2.ScalarRecord] - run_handle: str - def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.ScalarRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class UpsertMediaRequest(_message.Message): - __slots__ = ("data", "run_handle") - DATA_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - data: _containers.RepeatedCompositeFieldContainer[_data_pb2.MediaRecord] - run_handle: str - def __init__(self, data: _Optional[_Iterable[_Union[_data_pb2.MediaRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class UpsertLogsRequest(_message.Message): - __slots__ = ("logs", "run_handle") - LOGS_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - logs: _containers.RepeatedCompositeFieldContainer[_log_pb2.LogRecord] - run_handle: str - def __init__(self, logs: _Optional[_Iterable[_Union[_log_pb2.LogRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class UpsertSavesRequest(_message.Message): - __slots__ = ("saves", "run_handle") - SAVES_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - saves: _containers.RepeatedCompositeFieldContainer[_save_pb2.SaveRecord] - run_handle: str - def __init__(self, saves: _Optional[_Iterable[_Union[_save_pb2.SaveRecord, _Mapping]]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class DeliverRunFinishRequest(_message.Message): - __slots__ = ("finish_record", "run_handle") - FINISH_RECORD_FIELD_NUMBER: _ClassVar[int] - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - finish_record: _run_pb2.FinishRecord - run_handle: str - def __init__(self, finish_record: _Optional[_Union[_run_pb2.FinishRecord, _Mapping]] = ..., run_handle: _Optional[str] = ...) -> None: ... - -class DeliverRunFinishResponse(_message.Message): - __slots__ = ("success", "message") - SUCCESS_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FIELD_NUMBER: _ClassVar[int] - success: bool - message: str - def __init__(self, success: bool = ..., message: _Optional[str] = ...) -> None: ... - -class GetOperationStatsRequest(_message.Message): - __slots__ = ("run_handle",) - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - run_handle: str - def __init__(self, run_handle: _Optional[str] = ...) -> None: ... - -class GetOperationStatsResponse(_message.Message): - __slots__ = ("success", "message", "stats") - SUCCESS_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FIELD_NUMBER: _ClassVar[int] - STATS_FIELD_NUMBER: _ClassVar[int] - success: bool - message: str - stats: _operation_pb2.OperationStats - def __init__(self, success: bool = ..., message: _Optional[str] = ..., stats: _Optional[_Union[_operation_pb2.OperationStats, _Mapping]] = ...) -> None: ... - -class ConfirmRunFinishRequest(_message.Message): - __slots__ = ("run_handle",) - RUN_HANDLE_FIELD_NUMBER: _ClassVar[int] - run_handle: str - def __init__(self, run_handle: _Optional[str] = ...) -> None: ... - -class ConfirmRunFinishResponse(_message.Message): - __slots__ = ("success", "message") - SUCCESS_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FIELD_NUMBER: _ClassVar[int] - success: bool - message: str - def __init__(self, success: bool = ..., message: _Optional[str] = ...) -> None: ... diff --git a/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py b/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py deleted file mode 100644 index b68fda345..000000000 --- a/swanlab/proto/swanlab/grpc/core/v2/core_pb2_grpc.py +++ /dev/null @@ -1,567 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from swanlab.proto.swanlab.grpc.core.v2 import core_pb2 as swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2 - -GRPC_GENERATED_VERSION = '1.78.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in swanlab/grpc/core/v2/core_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class CoreServiceStub(object): - """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 - - 相对 v1 的关键变更: - - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; - 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; - 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 - v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetCapabilities = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/GetCapabilities', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.FromString, - _registered_method=True) - self.TeardownService = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/TeardownService', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.FromString, - _registered_method=True) - self.DeliverRunStart = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/DeliverRunStart', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.FromString, - _registered_method=True) - self.UpsertColumns = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/UpsertColumns', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.UpsertScalars = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/UpsertScalars', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.UpsertMedia = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/UpsertMedia', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.UpsertLogs = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/UpsertLogs', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.UpsertSaves = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/UpsertSaves', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - _registered_method=True) - self.DeliverRunFinish = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/DeliverRunFinish', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.FromString, - _registered_method=True) - self.GetOperationStats = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/GetOperationStats', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.FromString, - _registered_method=True) - self.ConfirmRunFinish = channel.unary_unary( - '/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish', - request_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.FromString, - _registered_method=True) - - -class CoreServiceServicer(object): - """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 - - 相对 v1 的关键变更: - - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; - 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; - 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 - v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 - """ - - def GetCapabilities(self, request, context): - """GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def TeardownService(self, request, context): - """TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeliverRunStart(self, request, context): - """DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpsertColumns(self, request, context): - """UpsertColumns 接收一组 ColumnRecord 并写入,每一条记录用于定义某一个指标 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpsertScalars(self, request, context): - """UpsertScalars 接收一组 ScalarRecord 并写入,每一条记录用于记录某一个指标的值 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpsertMedia(self, request, context): - """UpsertMedia 接收一组 MediaRecord 并写入,每一条记录用于记录某一个指标的值 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpsertLogs(self, request, context): - """UpsertLogs 接收一组 LogRecord 并写入,每一条记录对应一行用户的终端输出 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UpsertSaves(self, request, context): - """UpsertSaves 接收一组 SaveRecord 并写入,每一条记录对应一次 swanlab.save() 的文件保存或者swanlab内部保存操作 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeliverRunFinish(self, request, context): - """DeliverRunFinish 接收单条 FinishRecord,用于实验结束。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetOperationStats(self, request, context): - """GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ConfirmRunFinish(self, request, context): - """ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CoreServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetCapabilities': grpc.unary_unary_rpc_method_handler( - servicer.GetCapabilities, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.SerializeToString, - ), - 'TeardownService': grpc.unary_unary_rpc_method_handler( - servicer.TeardownService, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.SerializeToString, - ), - 'DeliverRunStart': grpc.unary_unary_rpc_method_handler( - servicer.DeliverRunStart, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.SerializeToString, - ), - 'UpsertColumns': grpc.unary_unary_rpc_method_handler( - servicer.UpsertColumns, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UpsertScalars': grpc.unary_unary_rpc_method_handler( - servicer.UpsertScalars, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UpsertMedia': grpc.unary_unary_rpc_method_handler( - servicer.UpsertMedia, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UpsertLogs': grpc.unary_unary_rpc_method_handler( - servicer.UpsertLogs, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UpsertSaves': grpc.unary_unary_rpc_method_handler( - servicer.UpsertSaves, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'DeliverRunFinish': grpc.unary_unary_rpc_method_handler( - servicer.DeliverRunFinish, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.SerializeToString, - ), - 'GetOperationStats': grpc.unary_unary_rpc_method_handler( - servicer.GetOperationStats, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.SerializeToString, - ), - 'ConfirmRunFinish': grpc.unary_unary_rpc_method_handler( - servicer.ConfirmRunFinish, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'swanlab.grpc.core.v2.CoreService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('swanlab.grpc.core.v2.CoreService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class CoreService(object): - """CoreService 是核心业务接口 v2,用于同步或异步地接收实验记录。 - - 相对 v1 的关键变更: - - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; - 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不再依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空,不会关闭 gRPC Server; - 4. GetOperationStats / ConfirmRunFinish 输入由 Empty 改为专用 request。 - v2 是独立命名空间,与 v1 不做 wire 兼容,调用方按版本整体切换。 - """ - - @staticmethod - def GetCapabilities(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/GetCapabilities', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetCapabilitiesResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def TeardownService(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/TeardownService', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.TeardownServiceResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeliverRunStart(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/DeliverRunStart', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunStartResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpsertColumns(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/UpsertColumns', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertColumnsRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpsertScalars(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/UpsertScalars', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertScalarsRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpsertMedia(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/UpsertMedia', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertMediaRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpsertLogs(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/UpsertLogs', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertLogsRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def UpsertSaves(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/UpsertSaves', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.UpsertSavesRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def DeliverRunFinish(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/DeliverRunFinish', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.DeliverRunFinishResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetOperationStats(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/GetOperationStats', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.GetOperationStatsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ConfirmRunFinish(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/swanlab.grpc.core.v2.CoreService/ConfirmRunFinish', - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v2_dot_core__pb2.ConfirmRunFinishResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) From a7e7f6239de079cd00990e59c30e6266830e3af2 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Sun, 20 Sep 2026 17:52:17 +0800 Subject: [PATCH 06/15] refactor: core start request --- core/internal/server/server_test.go | 8 +- core/proto/swanlab/grpc/core/v1/core.pb.go | 158 +++++------------- .../swanlab/grpc/core/v1/core_grpc.pb.go | 44 ++--- protos/swanlab/grpc/core/v1/core.proto | 26 +-- .../proto/swanlab/grpc/core/v1/core_pb2.py | 74 ++++---- .../proto/swanlab/grpc/core/v1/core_pb2.pyi | 30 +--- .../swanlab/grpc/core/v1/core_pb2_grpc.py | 41 ++--- 7 files changed, 145 insertions(+), 236 deletions(-) diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index c9ea77b8d..838b740ba 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -91,12 +91,14 @@ func TestTeardownServiceRejectsEmptyConfiguredToken(t *testing.T) { } } -func TestRunLevelRPCsUnimplemented(t *testing.T) { +// TestSkeletonRPCsUnimplemented 锁定 PR-1 骨架语义:READY 状态机(PR-2)落地前, +// 除 TeardownService 外的 RPC 一律 UNIMPLEMENTED,不得假成功。 +func TestSkeletonRPCsUnimplemented(t *testing.T) { env := newTestEnv(t, "owner-secret") ctx, cancel := context.WithTimeout(context.Background(), callTimeout) defer cancel() - if _, err := env.client.GetCapabilities(ctx, &corev1.GetCapabilitiesRequest{}); status.Code(err) != codes.Unimplemented { - t.Fatalf("GetCapabilities err = %v, want Unimplemented", err) + if _, err := env.client.SpinupService(ctx, &corev1.SpinupServiceRequest{OwnerToken: "owner-secret"}); status.Code(err) != codes.Unimplemented { + t.Fatalf("SpinupService err = %v, want Unimplemented", err) } if _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{}); status.Code(err) != codes.Unimplemented { t.Fatalf("UpsertScalars err = %v, want Unimplemented", err) diff --git a/core/proto/swanlab/grpc/core/v1/core.pb.go b/core/proto/swanlab/grpc/core/v1/core.pb.go index 9afd7546f..f58d0fe1e 100644 --- a/core/proto/swanlab/grpc/core/v1/core.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core.pb.go @@ -29,27 +29,28 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// GetCapabilities 请求。当前无字段,预留后续扩展。 -type GetCapabilitiesRequest struct { +// SpinupService 请求。 +type SpinupServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` + OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetCapabilitiesRequest) Reset() { - *x = GetCapabilitiesRequest{} +func (x *SpinupServiceRequest) Reset() { + *x = SpinupServiceRequest{} mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetCapabilitiesRequest) String() string { +func (x *SpinupServiceRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetCapabilitiesRequest) ProtoMessage() {} +func (*SpinupServiceRequest) ProtoMessage() {} -func (x *GetCapabilitiesRequest) ProtoReflect() protoreflect.Message { +func (x *SpinupServiceRequest) ProtoReflect() protoreflect.Message { mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -61,42 +62,39 @@ func (x *GetCapabilitiesRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCapabilitiesRequest.ProtoReflect.Descriptor instead. -func (*GetCapabilitiesRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use SpinupServiceRequest.ProtoReflect.Descriptor instead. +func (*SpinupServiceRequest) Descriptor() ([]byte, []int) { return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{0} } -// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, -// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 -type GetCapabilitiesResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProtocolVersion string `protobuf:"bytes,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` // 协议版本,与 Python SDK 期望版本锁步 - CoreVersion string `protobuf:"bytes,2,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` // core 二进制编译版本(构建管线注入) - Lifecycle bool `protobuf:"varint,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"` // 生命周期与进程管理能力 - SupportedModes []string `protobuf:"bytes,4,rep,name=supported_modes,json=supportedModes,proto3" json:"supported_modes,omitempty"` // 支持的 run 模式,如 online/local/offline - Store bool `protobuf:"varint,5,opt,name=store,proto3" json:"store,omitempty"` // 本地持久化能力 - Transport bool `protobuf:"varint,6,opt,name=transport,proto3" json:"transport,omitempty"` // 云端上传能力 - Payload bool `protobuf:"varint,7,opt,name=payload,proto3" json:"payload,omitempty"` // 内联 payload 能力(online skip_store 模式) - Sync bool `protobuf:"varint,8,opt,name=sync,proto3" json:"sync,omitempty"` // 离线数据同步能力 - MaxReceiveMessageBytes int64 `protobuf:"varint,9,opt,name=max_receive_message_bytes,json=maxReceiveMessageBytes,proto3" json:"max_receive_message_bytes,omitempty"` // 服务端单条消息接收上限,用于客户端协商 - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetCapabilitiesResponse) Reset() { - *x = GetCapabilitiesResponse{} +func (x *SpinupServiceRequest) GetOwnerToken() string { + if x != nil { + return x.OwnerToken + } + return "" +} + +// SpinupService 响应。无字段,返回即表示服务已 READY。 +type SpinupServiceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SpinupServiceResponse) Reset() { + *x = SpinupServiceResponse{} mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetCapabilitiesResponse) String() string { +func (x *SpinupServiceResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetCapabilitiesResponse) ProtoMessage() {} +func (*SpinupServiceResponse) ProtoMessage() {} -func (x *GetCapabilitiesResponse) ProtoReflect() protoreflect.Message { +func (x *SpinupServiceResponse) ProtoReflect() protoreflect.Message { mi := &file_swanlab_grpc_core_v1_core_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -108,74 +106,11 @@ func (x *GetCapabilitiesResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetCapabilitiesResponse.ProtoReflect.Descriptor instead. -func (*GetCapabilitiesResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use SpinupServiceResponse.ProtoReflect.Descriptor instead. +func (*SpinupServiceResponse) Descriptor() ([]byte, []int) { return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{1} } -func (x *GetCapabilitiesResponse) GetProtocolVersion() string { - if x != nil { - return x.ProtocolVersion - } - return "" -} - -func (x *GetCapabilitiesResponse) GetCoreVersion() string { - if x != nil { - return x.CoreVersion - } - return "" -} - -func (x *GetCapabilitiesResponse) GetLifecycle() bool { - if x != nil { - return x.Lifecycle - } - return false -} - -func (x *GetCapabilitiesResponse) GetSupportedModes() []string { - if x != nil { - return x.SupportedModes - } - return nil -} - -func (x *GetCapabilitiesResponse) GetStore() bool { - if x != nil { - return x.Store - } - return false -} - -func (x *GetCapabilitiesResponse) GetTransport() bool { - if x != nil { - return x.Transport - } - return false -} - -func (x *GetCapabilitiesResponse) GetPayload() bool { - if x != nil { - return x.Payload - } - return false -} - -func (x *GetCapabilitiesResponse) GetSync() bool { - if x != nil { - return x.Sync - } - return false -} - -func (x *GetCapabilitiesResponse) GetMaxReceiveMessageBytes() int64 { - if x != nil { - return x.MaxReceiveMessageBytes - } - return 0 -} - // TeardownService 请求。 type TeardownServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -999,18 +934,11 @@ var File_swanlab_grpc_core_v1_core_proto protoreflect.FileDescriptor const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "\n" + - "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n" + - "\x16GetCapabilitiesRequest\"\xcb\x02\n" + - "\x17GetCapabilitiesResponse\x12)\n" + - "\x10protocol_version\x18\x01 \x01(\tR\x0fprotocolVersion\x12!\n" + - "\fcore_version\x18\x02 \x01(\tR\vcoreVersion\x12\x1c\n" + - "\tlifecycle\x18\x03 \x01(\bR\tlifecycle\x12'\n" + - "\x0fsupported_modes\x18\x04 \x03(\tR\x0esupportedModes\x12\x14\n" + - "\x05store\x18\x05 \x01(\bR\x05store\x12\x1c\n" + - "\ttransport\x18\x06 \x01(\bR\ttransport\x12\x18\n" + - "\apayload\x18\a \x01(\bR\apayload\x12\x12\n" + - "\x04sync\x18\b \x01(\bR\x04sync\x129\n" + - "\x19max_receive_message_bytes\x18\t \x01(\x03R\x16maxReceiveMessageBytes\"9\n" + + "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"7\n" + + "\x14SpinupServiceRequest\x12\x1f\n" + + "\vowner_token\x18\x01 \x01(\tR\n" + + "ownerToken\"\x17\n" + + "\x15SpinupServiceResponse\"9\n" + "\x16TeardownServiceRequest\x12\x1f\n" + "\vowner_token\x18\x01 \x01(\tR\n" + "ownerToken\"\x19\n" + @@ -1069,9 +997,9 @@ const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "run_handle\x18\x01 \x01(\tR\trunHandle\"N\n" + "\x18ConfirmRunFinishResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + - "\amessage\x18\x02 \x01(\tR\amessage2\xd4\b\n" + - "\vCoreService\x12n\n" + - "\x0fGetCapabilities\x12,.swanlab.grpc.core.v1.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v1.GetCapabilitiesResponse\x12n\n" + + "\amessage\x18\x02 \x01(\tR\amessage2\xce\b\n" + + "\vCoreService\x12h\n" + + "\rSpinupService\x12*.swanlab.grpc.core.v1.SpinupServiceRequest\x1a+.swanlab.grpc.core.v1.SpinupServiceResponse\x12n\n" + "\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n" + "\x0fDeliverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n" + "\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n" + @@ -1098,8 +1026,8 @@ func file_swanlab_grpc_core_v1_core_proto_rawDescGZIP() []byte { var file_swanlab_grpc_core_v1_core_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_swanlab_grpc_core_v1_core_proto_goTypes = []any{ - (*GetCapabilitiesRequest)(nil), // 0: swanlab.grpc.core.v1.GetCapabilitiesRequest - (*GetCapabilitiesResponse)(nil), // 1: swanlab.grpc.core.v1.GetCapabilitiesResponse + (*SpinupServiceRequest)(nil), // 0: swanlab.grpc.core.v1.SpinupServiceRequest + (*SpinupServiceResponse)(nil), // 1: swanlab.grpc.core.v1.SpinupServiceResponse (*TeardownServiceRequest)(nil), // 2: swanlab.grpc.core.v1.TeardownServiceRequest (*TeardownServiceResponse)(nil), // 3: swanlab.grpc.core.v1.TeardownServiceResponse (*DeliverRunStartRequest)(nil), // 4: swanlab.grpc.core.v1.DeliverRunStartRequest @@ -1137,7 +1065,7 @@ var file_swanlab_grpc_core_v1_core_proto_depIdxs = []int32{ 23, // 7: swanlab.grpc.core.v1.UpsertSavesRequest.saves:type_name -> swanlab.save.v1.SaveRecord 24, // 8: swanlab.grpc.core.v1.DeliverRunFinishRequest.finish_record:type_name -> swanlab.run.v1.FinishRecord 25, // 9: swanlab.grpc.core.v1.GetOperationStatsResponse.stats:type_name -> swanlab.operation.v1.OperationStats - 0, // 10: swanlab.grpc.core.v1.CoreService.GetCapabilities:input_type -> swanlab.grpc.core.v1.GetCapabilitiesRequest + 0, // 10: swanlab.grpc.core.v1.CoreService.SpinupService:input_type -> swanlab.grpc.core.v1.SpinupServiceRequest 2, // 11: swanlab.grpc.core.v1.CoreService.TeardownService:input_type -> swanlab.grpc.core.v1.TeardownServiceRequest 4, // 12: swanlab.grpc.core.v1.CoreService.DeliverRunStart:input_type -> swanlab.grpc.core.v1.DeliverRunStartRequest 6, // 13: swanlab.grpc.core.v1.CoreService.UpsertColumns:input_type -> swanlab.grpc.core.v1.UpsertColumnsRequest @@ -1148,7 +1076,7 @@ var file_swanlab_grpc_core_v1_core_proto_depIdxs = []int32{ 11, // 18: swanlab.grpc.core.v1.CoreService.DeliverRunFinish:input_type -> swanlab.grpc.core.v1.DeliverRunFinishRequest 13, // 19: swanlab.grpc.core.v1.CoreService.GetOperationStats:input_type -> swanlab.grpc.core.v1.GetOperationStatsRequest 15, // 20: swanlab.grpc.core.v1.CoreService.ConfirmRunFinish:input_type -> swanlab.grpc.core.v1.ConfirmRunFinishRequest - 1, // 21: swanlab.grpc.core.v1.CoreService.GetCapabilities:output_type -> swanlab.grpc.core.v1.GetCapabilitiesResponse + 1, // 21: swanlab.grpc.core.v1.CoreService.SpinupService:output_type -> swanlab.grpc.core.v1.SpinupServiceResponse 3, // 22: swanlab.grpc.core.v1.CoreService.TeardownService:output_type -> swanlab.grpc.core.v1.TeardownServiceResponse 5, // 23: swanlab.grpc.core.v1.CoreService.DeliverRunStart:output_type -> swanlab.grpc.core.v1.DeliverRunStartResponse 26, // 24: swanlab.grpc.core.v1.CoreService.UpsertColumns:output_type -> google.protobuf.Empty diff --git a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go index c119e8cf1..c9e032da1 100644 --- a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go @@ -20,7 +20,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - CoreService_GetCapabilities_FullMethodName = "/swanlab.grpc.core.v1.CoreService/GetCapabilities" + CoreService_SpinupService_FullMethodName = "/swanlab.grpc.core.v1.CoreService/SpinupService" CoreService_TeardownService_FullMethodName = "/swanlab.grpc.core.v1.CoreService/TeardownService" CoreService_DeliverRunStart_FullMethodName = "/swanlab.grpc.core.v1.CoreService/DeliverRunStart" CoreService_UpsertColumns_FullMethodName = "/swanlab.grpc.core.v1.CoreService/UpsertColumns" @@ -39,15 +39,16 @@ const ( // // CoreService 是核心业务接口,用于同步或异步地接收实验记录。 // -// 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): +// 生命周期约定: // -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; // 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceClient interface { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) + // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport + // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 + SpinupService(ctx context.Context, in *SpinupServiceRequest, opts ...grpc.CallOption) (*SpinupServiceResponse, error) // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) @@ -79,10 +80,10 @@ func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient { return &coreServiceClient{cc} } -func (c *coreServiceClient) GetCapabilities(ctx context.Context, in *GetCapabilitiesRequest, opts ...grpc.CallOption) (*GetCapabilitiesResponse, error) { +func (c *coreServiceClient) SpinupService(ctx context.Context, in *SpinupServiceRequest, opts ...grpc.CallOption) (*SpinupServiceResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetCapabilitiesResponse) - err := c.cc.Invoke(ctx, CoreService_GetCapabilities_FullMethodName, in, out, cOpts...) + out := new(SpinupServiceResponse) + err := c.cc.Invoke(ctx, CoreService_SpinupService_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -195,15 +196,16 @@ func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *ConfirmRun // // CoreService 是核心业务接口,用于同步或异步地接收实验记录。 // -// 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): +// 生命周期约定: // -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; // 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceServer interface { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) + // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport + // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 + SpinupService(context.Context, *SpinupServiceRequest) (*SpinupServiceResponse, error) // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) @@ -235,8 +237,8 @@ type CoreServiceServer interface { // pointer dereference when methods are called. type UnimplementedCoreServiceServer struct{} -func (UnimplementedCoreServiceServer) GetCapabilities(context.Context, *GetCapabilitiesRequest) (*GetCapabilitiesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetCapabilities not implemented") +func (UnimplementedCoreServiceServer) SpinupService(context.Context, *SpinupServiceRequest) (*SpinupServiceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SpinupService not implemented") } func (UnimplementedCoreServiceServer) TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) { return nil, status.Error(codes.Unimplemented, "method TeardownService not implemented") @@ -289,20 +291,20 @@ func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) { s.RegisterService(&CoreService_ServiceDesc, srv) } -func _CoreService_GetCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetCapabilitiesRequest) +func _CoreService_SpinupService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SpinupServiceRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(CoreServiceServer).GetCapabilities(ctx, in) + return srv.(CoreServiceServer).SpinupService(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: CoreService_GetCapabilities_FullMethodName, + FullMethod: CoreService_SpinupService_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServiceServer).GetCapabilities(ctx, req.(*GetCapabilitiesRequest)) + return srv.(CoreServiceServer).SpinupService(ctx, req.(*SpinupServiceRequest)) } return interceptor(ctx, in, info, handler) } @@ -495,8 +497,8 @@ var CoreService_ServiceDesc = grpc.ServiceDesc{ HandlerType: (*CoreServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "GetCapabilities", - Handler: _CoreService_GetCapabilities_Handler, + MethodName: "SpinupService", + Handler: _CoreService_SpinupService_Handler, }, { MethodName: "TeardownService", diff --git a/protos/swanlab/grpc/core/v1/core.proto b/protos/swanlab/grpc/core/v1/core.proto index 76538f5d1..93ab7c7f0 100644 --- a/protos/swanlab/grpc/core/v1/core.proto +++ b/protos/swanlab/grpc/core/v1/core.proto @@ -17,13 +17,14 @@ option go_package = "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1 // // 生命周期约定: // -// 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; +// 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; // 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 service CoreService { - // GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 - rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); + // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport + // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 + rpc SpinupService(SpinupServiceRequest) returns (SpinupServiceResponse); // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 @@ -57,22 +58,13 @@ service CoreService { rpc ConfirmRunFinish(ConfirmRunFinishRequest) returns (ConfirmRunFinishResponse); } -// GetCapabilities 请求。当前无字段,预留后续扩展。 -message GetCapabilitiesRequest { +// SpinupService 请求。 +message SpinupServiceRequest { + string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 } -// GetCapabilities 响应,服务能力快照。能力位必须如实反映实现现状, -// 调用方应校验所需能力后再接入数据路径,能力缺失时不得继续写入。 -message GetCapabilitiesResponse { - string protocol_version = 1; // 协议版本,与 Python SDK 期望版本锁步 - string core_version = 2; // core 二进制编译版本(构建管线注入) - bool lifecycle = 3; // 生命周期与进程管理能力 - repeated string supported_modes = 4; // 支持的 run 模式,如 online/local/offline - bool store = 5; // 本地持久化能力 - bool transport = 6; // 云端上传能力 - bool payload = 7; // 内联 payload 能力(online skip_store 模式) - bool sync = 8; // 离线数据同步能力 - int64 max_receive_message_bytes = 9; // 服务端单条消息接收上限,用于客户端协商 +// SpinupService 响应。无字段,返回即表示服务已 READY。 +message SpinupServiceResponse { } // TeardownService 请求。 diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py index 74d176ffd..6f4035580 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py @@ -32,7 +32,7 @@ from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as swanlab_dot_settings_dot_core_dot_v1_dot_core__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x18\n\x16GetCapabilitiesRequest\"\xd9\x01\n\x17GetCapabilitiesResponse\x12\x18\n\x10protocol_version\x18\x01 \x01(\t\x12\x14\n\x0c\x63ore_version\x18\x02 \x01(\t\x12\x11\n\tlifecycle\x18\x03 \x01(\x08\x12\x17\n\x0fsupported_modes\x18\x04 \x03(\t\x12\r\n\x05store\x18\x05 \x01(\x08\x12\x11\n\ttransport\x18\x06 \x01(\x08\x12\x0f\n\x07payload\x18\x07 \x01(\x08\x12\x0c\n\x04sync\x18\x08 \x01(\x08\x12!\n\x19max_receive_message_bytes\x18\t \x01(\x03\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xd4\x08\n\x0b\x43oreService\x12n\n\x0fGetCapabilities\x12,.swanlab.grpc.core.v1.GetCapabilitiesRequest\x1a-.swanlab.grpc.core.v1.GetCapabilitiesResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"+\n\x14SpinupServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x17\n\x15SpinupServiceResponse\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xce\x08\n\x0b\x43oreService\x12h\n\rSpinupService\x12*.swanlab.grpc.core.v1.SpinupServiceRequest\x1a+.swanlab.grpc.core.v1.SpinupServiceResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -40,40 +40,40 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'ZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1' - _globals['_GETCAPABILITIESREQUEST']._serialized_start=320 - _globals['_GETCAPABILITIESREQUEST']._serialized_end=344 - _globals['_GETCAPABILITIESRESPONSE']._serialized_start=347 - _globals['_GETCAPABILITIESRESPONSE']._serialized_end=564 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=566 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=611 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=613 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=638 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=641 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=779 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=782 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=1004 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=1006 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=1105 - _globals['_UPSERTSCALARSREQUEST']._serialized_start=1107 - _globals['_UPSERTSCALARSREQUEST']._serialized_end=1201 - _globals['_UPSERTMEDIAREQUEST']._serialized_start=1203 - _globals['_UPSERTMEDIAREQUEST']._serialized_end=1294 - _globals['_UPSERTLOGSREQUEST']._serialized_start=1296 - _globals['_UPSERTLOGSREQUEST']._serialized_end=1381 - _globals['_UPSERTSAVESREQUEST']._serialized_start=1383 - _globals['_UPSERTSAVESREQUEST']._serialized_end=1467 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1469 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1567 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1569 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1629 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1631 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1677 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1679 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1793 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1795 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1840 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1842 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1902 - _globals['_CORESERVICE']._serialized_start=1905 - _globals['_CORESERVICE']._serialized_end=3013 + _globals['_SPINUPSERVICEREQUEST']._serialized_start=320 + _globals['_SPINUPSERVICEREQUEST']._serialized_end=363 + _globals['_SPINUPSERVICERESPONSE']._serialized_start=365 + _globals['_SPINUPSERVICERESPONSE']._serialized_end=388 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=390 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=435 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=437 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=462 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=465 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=603 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=606 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=828 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=830 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=929 + _globals['_UPSERTSCALARSREQUEST']._serialized_start=931 + _globals['_UPSERTSCALARSREQUEST']._serialized_end=1025 + _globals['_UPSERTMEDIAREQUEST']._serialized_start=1027 + _globals['_UPSERTMEDIAREQUEST']._serialized_end=1118 + _globals['_UPSERTLOGSREQUEST']._serialized_start=1120 + _globals['_UPSERTLOGSREQUEST']._serialized_end=1205 + _globals['_UPSERTSAVESREQUEST']._serialized_start=1207 + _globals['_UPSERTSAVESREQUEST']._serialized_end=1291 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1293 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1391 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1393 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1453 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1455 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1501 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1503 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1617 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1619 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1664 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1666 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1726 + _globals['_CORESERVICE']._serialized_start=1729 + _globals['_CORESERVICE']._serialized_end=2831 # @@protoc_insertion_point(module_scope) diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi index 7e4ca87c6..556813be4 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi @@ -14,32 +14,16 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor -class GetCapabilitiesRequest(_message.Message): +class SpinupServiceRequest(_message.Message): + __slots__ = ("owner_token",) + OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] + owner_token: str + def __init__(self, owner_token: _Optional[str] = ...) -> None: ... + +class SpinupServiceResponse(_message.Message): __slots__ = () def __init__(self) -> None: ... -class GetCapabilitiesResponse(_message.Message): - __slots__ = ("protocol_version", "core_version", "lifecycle", "supported_modes", "store", "transport", "payload", "sync", "max_receive_message_bytes") - PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int] - CORE_VERSION_FIELD_NUMBER: _ClassVar[int] - LIFECYCLE_FIELD_NUMBER: _ClassVar[int] - SUPPORTED_MODES_FIELD_NUMBER: _ClassVar[int] - STORE_FIELD_NUMBER: _ClassVar[int] - TRANSPORT_FIELD_NUMBER: _ClassVar[int] - PAYLOAD_FIELD_NUMBER: _ClassVar[int] - SYNC_FIELD_NUMBER: _ClassVar[int] - MAX_RECEIVE_MESSAGE_BYTES_FIELD_NUMBER: _ClassVar[int] - protocol_version: str - core_version: str - lifecycle: bool - supported_modes: _containers.RepeatedScalarFieldContainer[str] - store: bool - transport: bool - payload: bool - sync: bool - max_receive_message_bytes: int - def __init__(self, protocol_version: _Optional[str] = ..., core_version: _Optional[str] = ..., lifecycle: bool = ..., supported_modes: _Optional[_Iterable[str]] = ..., store: bool = ..., transport: bool = ..., payload: bool = ..., sync: bool = ..., max_receive_message_bytes: _Optional[int] = ...) -> None: ... - class TeardownServiceRequest(_message.Message): __slots__ = ("owner_token",) OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py index 3116c4712..19329c72f 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py @@ -29,9 +29,9 @@ class CoreServiceStub(object): """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 - 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + 生命周期约定: - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 @@ -43,10 +43,10 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ - self.GetCapabilities = channel.unary_unary( - '/swanlab.grpc.core.v1.CoreService/GetCapabilities', - request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, - response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.FromString, + self.SpinupService = channel.unary_unary( + '/swanlab.grpc.core.v1.CoreService/SpinupService', + request_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceRequest.SerializeToString, + response_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceResponse.FromString, _registered_method=True) self.TeardownService = channel.unary_unary( '/swanlab.grpc.core.v1.CoreService/TeardownService', @@ -103,16 +103,17 @@ def __init__(self, channel): class CoreServiceServicer(object): """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 - 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + 生命周期约定: - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ - def GetCapabilities(self, request, context): - """GetCapabilities 返回服务能力描述,用于启动阶段的能力协商(capability handshake)。 + def SpinupService(self, request, context): + """SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport + 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -192,10 +193,10 @@ def ConfirmRunFinish(self, request, context): def add_CoreServiceServicer_to_server(servicer, server): rpc_method_handlers = { - 'GetCapabilities': grpc.unary_unary_rpc_method_handler( - servicer.GetCapabilities, - request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.FromString, - response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.SerializeToString, + 'SpinupService': grpc.unary_unary_rpc_method_handler( + servicer.SpinupService, + request_deserializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceRequest.FromString, + response_serializer=swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceResponse.SerializeToString, ), 'TeardownService': grpc.unary_unary_rpc_method_handler( servicer.TeardownService, @@ -258,16 +259,16 @@ def add_CoreServiceServicer_to_server(servicer, server): class CoreService(object): """CoreService 是核心业务接口,用于同步或异步地接收实验记录。 - 生命周期约定(在协议正式发布前引入,作为一次性 breaking change 完成): + 生命周期约定: - 1. 服务级与 run 级生命周期严格分离:GetCapabilities / TeardownService 作用于整个服务进程; + 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ @staticmethod - def GetCapabilities(request, + def SpinupService(request, target, options=(), channel_credentials=None, @@ -280,9 +281,9 @@ def GetCapabilities(request, return grpc.experimental.unary_unary( request, target, - '/swanlab.grpc.core.v1.CoreService/GetCapabilities', - swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesRequest.SerializeToString, - swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.GetCapabilitiesResponse.FromString, + '/swanlab.grpc.core.v1.CoreService/SpinupService', + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceRequest.SerializeToString, + swanlab_dot_grpc_dot_core_dot_v1_dot_core__pb2.SpinupServiceResponse.FromString, options, channel_credentials, insecure, From 83d67892d2a2dcf8c5ecc3e96e517713df311a4b Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Sun, 20 Sep 2026 19:24:31 +0800 Subject: [PATCH 07/15] feat(core): add lifecycle --- core/cmd/swanlab-core/main.go | 21 +- .../pkg/process/parent_integration_test.go | 165 ++++++++ core/internal/pkg/process/parent_linux.go | 73 +++- core/internal/server/auth.go | 46 +++ core/internal/server/controller.go | 12 +- core/internal/server/lifecycle.go | 76 ++++ core/internal/server/lifecycle_test.go | 377 ++++++++++++++++++ core/internal/server/server.go | 148 ++++++- core/internal/server/server_test.go | 66 +-- core/internal/server/session.go | 96 +++++ 10 files changed, 1023 insertions(+), 57 deletions(-) create mode 100644 core/internal/server/auth.go create mode 100644 core/internal/server/lifecycle.go create mode 100644 core/internal/server/lifecycle_test.go create mode 100644 core/internal/server/session.go diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index 0c4911b12..22381b004 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -140,17 +140,24 @@ func run(args []string) int { return exitRunError } - grpcServer := grpc.NewServer() - ctrl := server.NewController(grpcServer, shutdownGrace) - server.NewService(ownerToken, ctrl).Register(grpcServer) - - // listen 与 server 初始化均成功后才写 port-file。 + // auth token 先于 gRPC server 创建生成:interceptor 与 port-file 使用同一值; + // 仅 --listen 手动调试(无 port-file)时不生成,鉴权随之关闭。 + authToken := "" if *portFilename != "" { - authToken, err2 := portinfo.NewAuthToken() + var err2 error + authToken, err2 = portinfo.NewAuthToken() if err2 != nil { console.Error("failed to generate auth token:", err2) return exitRunError } + } + + grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(server.UnaryAuthInterceptor(authToken))) + ctrl := server.NewController(grpcServer, shutdownGrace) + server.NewService(ownerToken, authToken, ctrl).Register(grpcServer) + + // listen 与 server 初始化均成功后才写 port-file。 + if *portFilename != "" { info := portinfo.Info{Protocol: portinfo.ProtocolVersion, AuthToken: authToken} switch addr := ln.Addr().(type) { case *net.UnixAddr: @@ -161,7 +168,7 @@ func run(args []string) int { console.Error("unrecognized listener address type:", ln.Addr()) return exitRunError } - if err2 = portinfo.WriteFile(*portFilename, &info); err2 != nil { + if err2 := portinfo.WriteFile(*portFilename, &info); err2 != nil { console.Error("failed to write port-file:", err2) return exitRunError } diff --git a/core/internal/pkg/process/parent_integration_test.go b/core/internal/pkg/process/parent_integration_test.go index 8cc862f20..63d1f35e9 100644 --- a/core/internal/pkg/process/parent_integration_test.go +++ b/core/internal/pkg/process/parent_integration_test.go @@ -6,8 +6,10 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" + "syscall" "testing" "time" @@ -119,6 +121,169 @@ func runMonitoredChildHelper() { os.Exit(0) } +// TestNotifyOnParentExitSurvivesSpawnThreadExit 验证进程级父死兜底(G5): +// +// 场景:父进程从非主线程 spawn 被监控子进程,该线程先退出(触发一次 PDEATHSIG +// 伪通知,必须被 PPID 复查过滤),父进程本体随后才退出。PDEATHSIG 绑定的是 +// spawn 线程,伪通知之后不再有信号,子进程只能依赖 pidfd/PPID 轮询兜底感知 +// 父进程退出,且不能因伪通知提前退出。 +// +// 三级结构与 TestNotifyOnParentExitNotifiesCaller 一致: +// 测试进程 -> parent helper(从 LockOSThread 的 goroutine spawn child)-> monitored child。 +func TestNotifyOnParentExitSurvivesSpawnThreadExit(t *testing.T) { + switch os.Getenv(parentExitHelperMode) { + case "thread-parent": + runThreadParentHelper() + case "thread-child": + runThreadMonitoredChildHelper() + } + + helperDir := t.TempDir() + cmd := exec.Command(os.Args[0], "-test.run=^TestNotifyOnParentExitSurvivesSpawnThreadExit$") + cmd.Env = append( + os.Environ(), + parentExitHelperMode+"=thread-parent", + "SWANLAB_PARENT_EXIT_DIR="+helperDir, + ) + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + // 1. child 就绪且 spawn 线程已退出后,child 不得因 PDEATHSIG 伪通知提前退出。 + if err := waitForMarker(filepath.Join(helperDir, "thread-dead"), 5*time.Second); err != nil { + killHelperProcess(t, filepath.Join(helperDir, "child.pid")) + t.Fatalf("wait for spawn thread exit: %v", err) + } + childPID, err := readPIDFile(filepath.Join(helperDir, "child.pid")) + if err != nil { + t.Fatalf("read child pid: %v", err) + } + if !processAlive(childPID) { + killHelperProcess(t, filepath.Join(helperDir, "child.pid")) + t.Fatal("child exited prematurely after spawn thread death (spurious PDEATHSIG not filtered)") + } + + // 2. parent helper 自行退出(thread-parent 模式在 thread-dead 后 os.Exit(0)), + // child 必须在兜底监控预算内感知并退出。 + if err := cmd.Wait(); err != nil { + t.Fatalf("thread parent helper failed: %v", err) + } + // parent 已退出但可能尚未被内核重新托管,轮询等待 child 退出。 + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + if !processAlive(childPID) { + return // child 正确随父进程退出 + } + time.Sleep(50 * time.Millisecond) + } + t.Fatal("child did not exit after parent process death (process-level fallback failed)") +} + +// runThreadParentHelper 在专用 OS 线程上 spawn child,随后结束该 goroutine +// 使线程销毁(等价于"Python 从非主线程 spawn 且该线程先退出"),确认 child +// 存活后退出自身,验证 child 的进程级父死监控。 +func runThreadParentHelper() { + helperDir := os.Getenv("SWANLAB_PARENT_EXIT_DIR") + type result struct { + pid int + err error + } + ch := make(chan result, 1) + go func() { + // 锁定 OS 线程:本 goroutine 返回时线程被 runtime 终止, + // 内核随之向 child 发送一次 PDEATHSIG 伪通知。 + runtime.LockOSThread() + cmd := exec.Command(os.Args[0], "-test.run=^TestNotifyOnParentExitSurvivesSpawnThreadExit$") + cmd.Env = append( + os.Environ(), + parentExitHelperMode+"=thread-child", + "SWANLAB_PARENT_EXIT_DIR="+helperDir, + ) + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + ch <- result{err: err} + return + } + ch <- result{pid: cmd.Process.Pid} + // goroutine 返回 -> 锁定线程销毁 -> PDEATHSIG 伪通知 + }() + res := <-ch + if res.err != nil { + fmt.Fprintf(os.Stderr, "start monitored child: %v\n", res.err) + os.Exit(2) + } + if err := os.WriteFile(filepath.Join(helperDir, "child.pid"), []byte(strconv.Itoa(res.pid)), 0o600); err != nil { + fmt.Fprintf(os.Stderr, "write child PID: %v\n", err) + os.Exit(2) + } + // 等 child 建立监控(ready 标记),再给线程销毁留出传播时间。 + if err := waitForMarkerFile(filepath.Join(helperDir, "ready"), 5*time.Second); err != nil { + fmt.Fprintf(os.Stderr, "monitored child did not become ready: %v\n", err) + os.Exit(2) + } + time.Sleep(300 * time.Millisecond) + if err := os.WriteFile(filepath.Join(helperDir, "thread-dead"), nil, 0o600); err != nil { + fmt.Fprintf(os.Stderr, "mark thread dead: %v\n", err) + os.Exit(2) + } + // 覆盖至少一个轮询周期,证明 child 在线程死亡 + 伪通知后仍然存活。 + time.Sleep(1500 * time.Millisecond) + os.Exit(0) +} + +// runThreadMonitoredChildHelper 与 runMonitoredChildHelper 类似, +// 但在父进程退出后写 exited 标记再退出,供测试区分"随父退出"与"提前退出"。 +func runThreadMonitoredChildHelper() { + helperDir := os.Getenv("SWANLAB_PARENT_EXIT_DIR") + parentExited, err := NotifyOnParentExit(os.Getppid()) + if err != nil { + fmt.Fprintf(os.Stderr, "monitor parent: %v\n", err) + os.Exit(2) + } + if err := os.WriteFile(filepath.Join(helperDir, "ready"), nil, 0o600); err != nil { + fmt.Fprintf(os.Stderr, "mark child ready: %v\n", err) + os.Exit(2) + } + // process 包只负责发出通知;由调用方决定收到通知后的退出方式。 + <-parentExited + _ = os.WriteFile(filepath.Join(helperDir, "exited"), nil, 0o600) + os.Exit(0) +} + +func readPIDFile(path string) (int, error) { + data, err := os.ReadFile(path) + if err != nil { + return 0, err + } + return strconv.Atoi(string(data)) +} + +// processAlive 通过 signal 0 探测进程是否存在(不含僵尸态判断, +// 测试场景中进程由测试进程树的子孙构成,不会被长期悬挂为僵尸)。 +func processAlive(pid int) bool { + proc, err := os.FindProcess(pid) + if err != nil { + return false + } + return proc.Signal(syscall.Signal(0)) == nil +} + +func waitForMarker(path string, timeout time.Duration) error { + return waitForMarkerFile(path, timeout) +} + +func waitForMarkerFile(path string, timeout time.Duration) error { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if _, err := os.Stat(path); err == nil { + return nil + } + time.Sleep(10 * time.Millisecond) + } + return fmt.Errorf("marker %s not found within %s", path, timeout) +} + func killHelperProcess(t *testing.T, pidPath string) { data, err := os.ReadFile(pidPath) if err != nil { diff --git a/core/internal/pkg/process/parent_linux.go b/core/internal/pkg/process/parent_linux.go index ba47e8365..22e9feecc 100644 --- a/core/internal/pkg/process/parent_linux.go +++ b/core/internal/pkg/process/parent_linux.go @@ -6,42 +6,99 @@ import ( "fmt" "os" "os/signal" + "sync" + "time" "golang.org/x/sys/unix" "github.com/swanhubx/swanlab/core/internal/pkg/console" ) +// parentPollInterval 是进程级兜底监控的轮询间隔:pidfd 不可用时退化为 +// 低频 PID/PPID 轮询,在退出响应速度和常驻唤醒开销之间取平衡。 +const parentPollInterval = time.Second + func notifyOnParentExit(parentPID int) (<-chan struct{}, error) { parentExited := make(chan struct{}) + var closeOnce sync.Once + notify := func() { + closeOnce.Do(func() { + console.Debugf("Parent process %d exited", parentPID) + close(parentExited) + }) + } + + // 快路径:PR_SET_PDEATHSIG 绑定的是调用 prctl 时的父进程线程。 + // 若 Python 从非主线程 spawn 且该线程先于进程退出,PDEATHSIG 只会触发一次 + // 伪通知,由下方的 PPID 复查过滤;此后不会再有信号,必须依赖进程级兜底。 parentDeathSignals := make(chan os.Signal, 1) signal.Notify(parentDeathSignals, unix.SIGUSR1) - - // PR_SET_PDEATHSIG 绑定的是调用 prctl 时的父进程,而不是任意指定 PID。 - // 外层和下方的 PID 校验共同保证该父进程就是调用方预期的 parentPID。 if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGUSR1), 0, 0, 0); err != nil { signal.Stop(parentDeathSignals) return nil, fmt.Errorf("set parent death signal: %w", err) } - // 父进程可能恰好在外层首次检查与 prctl 调用之间退出,必须再次确认。 if err := checkParent(parentPID); err != nil { signal.Stop(parentDeathSignals) return nil, err } - go func() { defer signal.Stop(parentDeathSignals) for range parentDeathSignals { - // SIGUSR1 也可能来自其他进程;只有父 PID 改变才视为父进程退出。 + // SIGUSR1 也可能来自其他进程或已退出的 spawn 线程; + // 只有父 PID 改变才视为父进程退出。 if checkParent(parentPID) == nil { continue } - console.Debugf("Parent process %d exited", parentPID) - close(parentExited) + notify() return } }() + // 进程级兜底:监控父进程本身而非创建本进程的线程。 + go func() { + watchParentProcess(parentPID) + notify() + }() + return parentExited, nil } + +// watchParentProcess 阻塞直到父进程退出。优先使用 pidfd(内核级通知), +// 不可用或中途出错时退化为低频 PPID 轮询。 +func watchParentProcess(parentPID int) { + if fd, err := unix.PidfdOpen(parentPID, 0); err == nil { + defer func() { _ = unix.Close(fd) }() + fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} + for { + n, err := unix.Poll(fds, -1) + if err == unix.EINTR { + continue + } + if err != nil { + // pidfd 等待异常(如被信号打断外的错误),退化为轮询兜底。 + break + } + if n > 0 { + return // 进程退出,pidfd 可读 + } + } + } + pollParentExit(parentPID) +} + +// pollParentExit 以固定间隔检查 PPID 是否改变;父进程存活期间 PPID 保持不变, +// 整个进程退出后被重新托管(init/subreaper),PPID 随之改变。 +// goroutine 启动前后存在极短竞态窗口,进入等待循环前先立即检查一次。 +func pollParentExit(parentPID int) { + if checkParent(parentPID) != nil { + return + } + ticker := time.NewTicker(parentPollInterval) + defer ticker.Stop() + for range ticker.C { + if checkParent(parentPID) != nil { + return + } + } +} diff --git a/core/internal/server/auth.go b/core/internal/server/auth.go new file mode 100644 index 000000000..6043310fc --- /dev/null +++ b/core/internal/server/auth.go @@ -0,0 +1,46 @@ +package server + +import ( + "context" + "crypto/hmac" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// AuthTokenMetadataKey 是每个 RPC 必须携带的 auth token metadata 键, +// token 值来自 port-file 的 auth 字段。 +const AuthTokenMetadataKey = "x-swanlab-core-auth-token" + +// UnaryAuthInterceptor 校验每个 unary RPC 的 auth token,使用常量时间比较, +// 拒绝缺失或错误的 token。expectedToken 为空表示未启用鉴权,仅限 --listen +// 手动调试场景;SDK 启动约定(--port-filename)总会生成并配置 token。 +func UnaryAuthInterceptor(expectedToken string) grpc.UnaryServerInterceptor { + return func( + ctx context.Context, + req any, + _ *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, + ) (any, error) { + if expectedToken != "" && !requestAuthorized(ctx, expectedToken) { + return nil, status.Error(codes.PermissionDenied, "invalid auth token") + } + return handler(ctx, req) + } +} + +// requestAuthorized 从 incoming metadata 中提取 auth token 并做常量时间比较。 +// token 值不得出现在日志或错误消息中。 +func requestAuthorized(ctx context.Context, expectedToken string) bool { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return false + } + values := md.Get(AuthTokenMetadataKey) + if len(values) != 1 { + return false + } + return hmac.Equal([]byte(values[0]), []byte(expectedToken)) +} diff --git a/core/internal/server/controller.go b/core/internal/server/controller.go index 73eaa4211..30267215e 100644 --- a/core/internal/server/controller.go +++ b/core/internal/server/controller.go @@ -9,16 +9,18 @@ import ( "github.com/swanhubx/swanlab/core/internal/pkg/console" ) -// Controller 仲裁服务的统一关闭路径。 +// Controller 仲裁服务的统一关闭路径,并持有服务状态机。 // // Teardown RPC、SIGINT/SIGTERM、父进程退出通知与 Serve 异常都汇入同一条 // 收尾序列:先 GracefulStop 等待在途请求完成,超过 grace 时限后强制 Stop, // 并保证 Serve 一定返回。Shutdown 幂等,多次触发只执行一次。 +// 进入收尾时服务状态转为 STOPPING,收尾完成后转为 CLOSED。 type Controller struct { server *grpc.Server grace time.Duration once sync.Once done chan struct{} + lc *Lifecycle } // NewController 包装一个 gRPC Server,grace 为优雅关闭的等待上限。 @@ -27,12 +29,19 @@ func NewController(g *grpc.Server, grace time.Duration) *Controller { server: g, grace: grace, done: make(chan struct{}), + lc: newLifecycle(), } } +// Lifecycle 返回由本 controller 仲裁的服务状态机。 +func (c *Controller) Lifecycle() *Lifecycle { + return c.lc +} + // Shutdown 幂等触发关闭;cause 仅用于日志,标识关闭来源。 func (c *Controller) Shutdown(cause string) { c.once.Do(func() { + c.lc.BeginStopping() console.Infof("core service shutting down (%s)", cause) go func() { defer close(c.done) @@ -47,6 +56,7 @@ func (c *Controller) Shutdown(cause string) { c.server.Stop() <-graceful } + c.lc.Close() }() }) } diff --git a/core/internal/server/lifecycle.go b/core/internal/server/lifecycle.go new file mode 100644 index 000000000..76aee20b2 --- /dev/null +++ b/core/internal/server/lifecycle.go @@ -0,0 +1,76 @@ +package server + +import "sync" + +// ServiceState 是服务级生命周期状态,只存在于 Go 内存,不上 proto。 +// 线上表达是 gRPC 状态码:READY 之前的 run RPC 返回 FAILED_PRECONDITION, +// 关闭仲裁由 Controller 汇入统一关闭路径。 +type ServiceState int32 + +const ( + // StateNotReady 服务已监听但未完成 Spinup,拒绝 run 级 RPC。 + StateNotReady ServiceState = iota + // StateReady SpinupService 成功,接受 run 级 RPC。 + StateReady + // StateStopping 关闭序列进行中(Teardown/信号/父死/serve 错误)。 + StateStopping + // StateClosed 关闭序列完成。 + StateClosed +) + +// Lifecycle 是服务状态机的并发安全实现。 +// +// 转换:NOT_READY → READY(SpinupService,READY 下幂等); +// NOT_READY/READY → STOPPING(Controller.Shutdown,幂等);STOPPING → CLOSED(关闭完成)。 +type Lifecycle struct { + mu sync.RWMutex + state ServiceState +} + +func newLifecycle() *Lifecycle { + return &Lifecycle{state: StateNotReady} +} + +// Get 返回当前服务状态。 +func (l *Lifecycle) Get() ServiceState { + l.mu.RLock() + defer l.mu.RUnlock() + return l.state +} + +// Ready 报告服务是否处于 READY。 +func (l *Lifecycle) Ready() bool { + return l.Get() == StateReady +} + +// Spinup 幂等执行 NOT_READY → READY;READY 下重复调用成功; +// 已进入 STOPPING/CLOSED 时返回 false,由调用方映射为 FAILED_PRECONDITION。 +func (l *Lifecycle) Spinup() bool { + l.mu.Lock() + defer l.mu.Unlock() + switch l.state { + case StateNotReady: + l.state = StateReady + return true + case StateReady: + return true + default: + return false + } +} + +// BeginStopping 幂等进入 STOPPING;已在 STOPPING/CLOSED 时为 no-op。 +func (l *Lifecycle) BeginStopping() { + l.mu.Lock() + defer l.mu.Unlock() + if l.state == StateNotReady || l.state == StateReady { + l.state = StateStopping + } +} + +// Close 在关闭序列完成后进入 CLOSED;幂等。 +func (l *Lifecycle) Close() { + l.mu.Lock() + defer l.mu.Unlock() + l.state = StateClosed +} diff --git a/core/internal/server/lifecycle_test.go b/core/internal/server/lifecycle_test.go new file mode 100644 index 000000000..d95d53386 --- /dev/null +++ b/core/internal/server/lifecycle_test.go @@ -0,0 +1,377 @@ +package server + +import ( + "fmt" + "sync" + "testing" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" + operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" + runv1 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" +) + +// spinup 是测试辅助:用正确的 owner/auth token 把服务置为 READY。 +func spinup(t *testing.T, env *testEnv) { + t.Helper() + if _, err := env.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); err != nil { + t.Fatalf("SpinupService: %v", err) + } +} + +// startRun 是测试辅助:创建一个 run 会话并返回 run_handle。 +func startRun(t *testing.T, env *testEnv) string { + t.Helper() + resp, err := env.client.DeliverRunStart( + authCtx(t, testAuthToken), + &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}, + ) + if err != nil { + t.Fatalf("DeliverRunStart: %v", err) + } + if !resp.GetSuccess() { + t.Fatal("DeliverRunStart success = false") + } + if resp.GetRunHandle() == "" { + t.Fatal("DeliverRunStart returned empty run_handle") + } + return resp.GetRunHandle() +} + +func TestLifecycleStateTransitions(t *testing.T) { + lc := newLifecycle() + if lc.Get() != StateNotReady { + t.Fatalf("initial state = %v, want StateNotReady", lc.Get()) + } + if !lc.Spinup() { + t.Fatal("Spinup from NOT_READY must succeed") + } + if lc.Get() != StateReady { + t.Fatalf("state after spinup = %v, want StateReady", lc.Get()) + } + if !lc.Spinup() { + t.Fatal("repeated Spinup in READY must succeed (idempotent)") + } + lc.BeginStopping() + if lc.Get() != StateStopping { + t.Fatalf("state after BeginStopping = %v, want StateStopping", lc.Get()) + } + lc.BeginStopping() // 幂等 + if lc.Spinup() { + t.Fatal("Spinup in STOPPING must be rejected") + } + lc.Close() + if lc.Get() != StateClosed { + t.Fatalf("state after Close = %v, want StateClosed", lc.Get()) + } + lc.Close() // 幂等 +} + +func TestAuthInterceptor(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + ctx := plainCtx(t) + + // 无 auth metadata:被 interceptor 拒绝 + if _, err := env.client.SpinupService(ctx, &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); status.Code(err) != codes.PermissionDenied { + t.Fatalf("no-auth SpinupService err = %v, want PermissionDenied", err) + } + // 错误 auth token + wrong := authCtx(t, "wrong-auth") + if _, err := env.client.SpinupService(wrong, &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); status.Code(err) != codes.PermissionDenied { + t.Fatalf("wrong-auth SpinupService err = %v, want PermissionDenied", err) + } + select { + case <-env.ctrl.Done(): + t.Fatal("auth failure must not trigger shutdown") + case <-afterWindow(): + } +} + +func TestSpinupOwnerCheck(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + // 正确 auth 但错误 owner token + _, err := env.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: "wrong-owner"}) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("wrong-owner SpinupService err = %v, want PermissionDenied", err) + } + // 未配置 owner token 的服务一律拒绝 + env2 := newTestEnv(t, "", testAuthToken) + _, err = env2.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: "any"}) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("empty-configured-owner SpinupService err = %v, want PermissionDenied", err) + } +} + +func TestSpinupIdempotent(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + spinup(t, env) // 重复 Spinup 在 READY 下幂等成功 +} + +func TestRunRPCsRejectedBeforeReady(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + ctx := authCtx(t, testAuthToken) + checks := []struct { + name string + call func() error + }{ + { + "DeliverRunStart", + func() error { + _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) + return err + }, + }, + { + "UpsertScalars", + func() error { + _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: "any"}) + return err + }, + }, + { + "GetOperationStats", + func() error { + _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "any"}) + return err + }, + }, + { + "DeliverRunFinish", + func() error { + _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: "any"}) + return err + }, + }, + { + "ConfirmRunFinish", + func() error { + _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: "any"}) + return err + }, + }, + } + for _, tc := range checks { + if err := tc.call(); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("%s before ready: err = %v, want FailedPrecondition", tc.name, err) + } + } +} + +func TestUpsertsUnimplementedAfterReady(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + handle := startRun(t, env) + ctx := authCtx(t, testAuthToken) + for _, tc := range []struct { + name string + call func() error + }{ + { + "UpsertColumns", + func() error { + _, err := env.client.UpsertColumns(ctx, &corev1.UpsertColumnsRequest{RunHandle: handle}) + return err + }, + }, + { + "UpsertScalars", + func() error { + _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: handle}) + return err + }, + }, + { + "UpsertMedia", + func() error { + _, err := env.client.UpsertMedia(ctx, &corev1.UpsertMediaRequest{RunHandle: handle}) + return err + }, + }, + { + "UpsertLogs", + func() error { + _, err := env.client.UpsertLogs(ctx, &corev1.UpsertLogsRequest{RunHandle: handle}) + return err + }, + }, + { + "UpsertSaves", + func() error { + _, err := env.client.UpsertSaves(ctx, &corev1.UpsertSavesRequest{RunHandle: handle}) + return err + }, + }, + } { + if err := tc.call(); status.Code(err) != codes.Unimplemented { + t.Fatalf("%s after ready: err = %v, want Unimplemented", tc.name, err) + } + } +} + +func TestDeliverRunStartRequiresStartRecord(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + _, err := env.client.DeliverRunStart(authCtx(t, testAuthToken), &corev1.DeliverRunStartRequest{}) + if status.Code(err) != codes.InvalidArgument { + t.Fatalf("DeliverRunStart without start_record: err = %v, want InvalidArgument", err) + } +} + +func TestHandleValidation(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + ctx := authCtx(t, testAuthToken) + + // 空 handle:InvalidArgument + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{}); status.Code(err) != codes.InvalidArgument { + t.Fatalf("empty handle stats err = %v, want InvalidArgument", err) + } + // 未知 handle:NotFound + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "no-such-handle"}); status.Code(err) != codes.NotFound { + t.Fatalf("unknown handle stats err = %v, want NotFound", err) + } +} + +func TestRunLifecycleCoreStateMapping(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + handle := startRun(t, env) + ctx := authCtx(t, testAuthToken) + + // start 后:RUNNING + resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil || !resp.GetSuccess() { + t.Fatalf("stats before finish: err = %v, resp = %v", err, resp) + } + if resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_RUNNING { + t.Fatalf("stats state before finish = %v, want RUNNING", resp.GetStats().GetState()) + } + + // finish 后:FINISHED(骨架无数据可排空) + if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("DeliverRunFinish: %v", err) + } + resp, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil { + t.Fatalf("stats after finish: %v", err) + } + if resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { + t.Fatalf("stats state after finish = %v, want FINISHED", resp.GetStats().GetState()) + } + + // confirm:释放会话,但绝不关闭 server + if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("ConfirmRunFinish: %v", err) + } + select { + case <-env.ctrl.Done(): + t.Fatal("ConfirmRunFinish must not shut down the server") + case <-afterWindow(): + } + // confirm 后 handle 失效 + if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { + t.Fatalf("stats after confirm err = %v, want NotFound", err) + } + if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { + t.Fatalf("repeat confirm err = %v, want NotFound", err) + } +} + +func TestTwoRunHandleIsolation(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + handleA, handleB := startRun(t, env), startRun(t, env) + if handleA == handleB { + t.Fatal("two runs must receive distinct run_handles") + } + ctx := authCtx(t, testAuthToken) + + // 只结束 A:A 报 FINISHED,B 仍是 RUNNING,互不串扰 + if _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handleA}); err != nil { + t.Fatalf("finish A: %v", err) + } + for _, tc := range []struct { + handle string + want operationv1.CoreState + }{ + {handleA, operationv1.CoreState_CORE_STATE_FINISHED}, + {handleB, operationv1.CoreState_CORE_STATE_RUNNING}, + } { + resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: tc.handle}) + if err != nil { + t.Fatalf("stats %s: %v", tc.handle, err) + } + if resp.GetStats().GetState() != tc.want { + t.Fatalf("stats %s state = %v, want %v", tc.handle, resp.GetStats().GetState(), tc.want) + } + } + + // confirm A:B 仍可正常走完生命周期 + if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handleA}); err != nil { + t.Fatalf("confirm A: %v", err) + } + if _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handleB}); err != nil { + t.Fatalf("finish B: %v", err) + } + resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handleB}) + if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { + t.Fatalf("stats B after finish: err = %v, state = %v", err, resp.GetStats().GetState()) + } +} + +// TestConcurrentRunSessions 模拟两个并发 client 各自跑完生命周期, +// 配合 -race 验证 session registry 的并发安全与会话隔离。 +func TestConcurrentRunSessions(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + spinup(t, env) + + const clients = 4 + var wg sync.WaitGroup + errs := make(chan error, clients) + for i := 0; i < clients; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + ctx := authCtx(t, testAuthToken) + resp, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) + if err != nil { + errs <- fmt.Errorf("client %d start: %w", id, err) + return + } + handle := resp.GetRunHandle() + for r := 0; r < 20; r++ { + if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); err != nil { + errs <- fmt.Errorf("client %d stats: %w", id, err) + return + } + } + if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { + errs <- fmt.Errorf("client %d finish: %w", id, err) + return + } + statsResp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil || statsResp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { + errs <- fmt.Errorf("client %d stats after finish: err = %v, state = %v", id, err, statsResp.GetStats().GetState()) + return + } + if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { + errs <- fmt.Errorf("client %d confirm: %w", id, err) + return + } + }(i) + } + wg.Wait() + close(errs) + for err := range errs { + t.Error(err) + } +} + +// afterWindow 返回 noShutdownWindow 时长通道,用于断言"未触发关闭"。 +func afterWindow() <-chan time.Time { + return time.After(noShutdownWindow) +} diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 6b3dff07a..0ff74714e 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -1,7 +1,12 @@ // Package server 提供 swanlab-core 的 gRPC 服务端实现。 // -// 当前为脚手架阶段:CoreService 已注册并可完成服务级关闭, -// 鉴权 interceptor、capability 应答与 run 会话路由在后续迭代接入。 +// 生命周期(P0 骨架): +// - auth interceptor 校验每个 RPC 的 auth token(port-file 回报); +// - SpinupService 是 owner-only、幂等的 READY 屏障; +// - 五类 Upsert* 在 store/transport 实现前统一返回 UNIMPLEMENTED,不假成功; +// - DeliverRunStart 创建 run_handle 会话,finish/stats/confirm 按 handle 路由; +// - ConfirmRunFinish 只释放对应会话,绝不关闭 gRPC Server; +// - TeardownService 校验 owner token,触发 controller 的统一关闭路径。 package server import ( @@ -11,27 +16,36 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" + operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" ) // Service 实现 CoreService。 // -// 除 TeardownService 外的所有 RPC 暂由嵌入的 Unimplemented 实现接管, -// 返回 UNIMPLEMENTED;run 级接口在会话路由迭代中逐步补齐。 +// 除 Teardown/Spinup 与 run 生命周期 RPC 外,五类业务 upsert 由嵌入的 +// Unimplemented 接管并显式改写为 UNIMPLEMENTED(在 READY 门控之后)。 type Service struct { corev1.UnimplementedCoreServiceServer ownerToken string + authToken string controller *Controller + lc *Lifecycle + registry *sessionRegistry } // NewService 创建服务实例。ownerToken 为 spawn owner 通过私有文件传入的 -// 服务级令牌,是唯一允许触发 TeardownService 的凭证。 -func NewService(ownerToken string, controller *Controller) *Service { +// 服务级令牌,是唯一允许触发 Spinup/Teardown 的凭证;authToken 为 +// port-file 回报给所有 client 的 RPC 鉴权令牌。 +func NewService(ownerToken, authToken string, controller *Controller) *Service { return &Service{ ownerToken: ownerToken, + authToken: authToken, controller: controller, + lc: controller.Lifecycle(), + registry: newSessionRegistry(), } } @@ -40,13 +54,131 @@ func (s *Service) Register(g *grpc.Server) { corev1.RegisterCoreServiceServer(g, s) } +// verifyOwner 常量时间校验 owner token;token 值不得出现在日志或错误消息中。 +func (s *Service) verifyOwner(token string) error { + if s.ownerToken == "" || !hmac.Equal([]byte(token), []byte(s.ownerToken)) { + return status.Error(codes.PermissionDenied, "invalid owner token") + } + return nil +} + +// requireReady 拒绝 READY 之前的 run 级 RPC。 +func (s *Service) requireReady() error { + if s.lc.Ready() { + return nil + } + return status.Error(codes.FailedPrecondition, "service is not ready; call SpinupService first") +} + +// rejectUpsert 是五类 upsert 的统一应答:READY 门控通过后必须返回 +// UNIMPLEMENTED——store/transport 未实现前不得用 Empty 成功假接收。 +func (s *Service) rejectUpsert() error { + if err := s.requireReady(); err != nil { + return err + } + return status.Error(codes.Unimplemented, "upsert data path is not implemented in this core build") +} + +// SpinupService 完成 owner 校验并把服务置为 READY,幂等; +// 已进入 STOPPING/CLOSED 时拒绝。 +func (s *Service) SpinupService(_ context.Context, req *corev1.SpinupServiceRequest) (*corev1.SpinupServiceResponse, error) { + if err := s.verifyOwner(req.GetOwnerToken()); err != nil { + return nil, err + } + if !s.lc.Spinup() { + return nil, status.Error(codes.FailedPrecondition, "service is stopping or closed") + } + return &corev1.SpinupServiceResponse{}, nil +} + // TeardownService 关闭整个服务进程,仅接受正确的 owner token。 // 校验使用常量时间比较;token 值不得出现在日志或错误消息中。 func (s *Service) TeardownService(_ context.Context, req *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { - if s.ownerToken == "" || !hmac.Equal([]byte(req.OwnerToken), []byte(s.ownerToken)) { - return nil, status.Error(codes.PermissionDenied, "invalid owner token") + if err := s.verifyOwner(req.GetOwnerToken()); err != nil { + return nil, err } // 异步触发统一关闭路径,保证本响应先于连接关闭送达调用方。 s.controller.Shutdown("teardown") return &corev1.TeardownServiceResponse{}, nil } + +// DeliverRunStart 创建 run 会话并返回非空 opaque run_handle。 +func (s *Service) DeliverRunStart(_ context.Context, req *corev1.DeliverRunStartRequest) (*corev1.DeliverRunStartResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + if req.GetStartRecord() == nil { + return nil, status.Error(codes.InvalidArgument, "start_record must not be nil") + } + handle, _, err := s.registry.create() + if err != nil { + return nil, err + } + // P0 骨架:仅回显最小有效结果,不落盘、不上传、不访问云端。 + return &corev1.DeliverRunStartResponse{ + Success: true, + Run: req.GetStartRecord(), + NewExperiment: true, + RunHandle: handle, + }, nil +} + +func (s *Service) UpsertColumns(_ context.Context, _ *corev1.UpsertColumnsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *Service) UpsertScalars(_ context.Context, _ *corev1.UpsertScalarsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *Service) UpsertMedia(_ context.Context, _ *corev1.UpsertMediaRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *Service) UpsertLogs(_ context.Context, _ *corev1.UpsertLogsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *Service) UpsertSaves(_ context.Context, _ *corev1.UpsertSavesRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +// DeliverRunFinish 只结束对应会话的 run,服务保持存活。 +func (s *Service) DeliverRunFinish(_ context.Context, req *corev1.DeliverRunFinishRequest) (*corev1.DeliverRunFinishResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + session, err := s.registry.lookup(req.GetRunHandle()) + if err != nil { + return nil, err + } + session.finish() + return &corev1.DeliverRunFinishResponse{Success: true}, nil +} + +// GetOperationStats 返回对应会话的 CoreState 映射(run 级数据排空轴)。 +func (s *Service) GetOperationStats(_ context.Context, req *corev1.GetOperationStatsRequest) (*corev1.GetOperationStatsResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + session, err := s.registry.lookup(req.GetRunHandle()) + if err != nil { + return nil, err + } + return &corev1.GetOperationStatsResponse{ + Success: true, + Stats: &operationv1.OperationStats{State: session.coreState()}, + }, nil +} + +// ConfirmRunFinish 确认对应会话已排空并释放资源,绝不关闭 gRPC Server。 +func (s *Service) ConfirmRunFinish(_ context.Context, req *corev1.ConfirmRunFinishRequest) (*corev1.ConfirmRunFinishResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + if _, err := s.registry.lookup(req.GetRunHandle()); err != nil { + return nil, err + } + s.registry.release(req.GetRunHandle()) + return &corev1.ConfirmRunFinishResponse{Success: true}, nil +} diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index 838b740ba..34213a31e 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" @@ -20,6 +21,9 @@ const ( testGrace = 2 * time.Second noShutdownWindow = 200 * time.Millisecond callTimeout = 2 * time.Second + + testOwnerToken = "owner-secret" + testAuthToken = "auth-secret" ) type testEnv struct { @@ -27,12 +31,12 @@ type testEnv struct { ctrl *Controller } -// newTestEnv 在内存连接上启动完整服务端,返回客户端句柄与关闭控制器。 -func newTestEnv(t *testing.T, ownerToken string) *testEnv { +// newTestEnv 在内存连接上启动完整服务端(含 auth interceptor),返回客户端句柄与关闭控制器。 +func newTestEnv(t *testing.T, ownerToken, authToken string) *testEnv { t.Helper() - g := grpc.NewServer() + g := grpc.NewServer(grpc.ChainUnaryInterceptor(UnaryAuthInterceptor(authToken))) ctrl := NewController(g, testGrace) - NewService(ownerToken, ctrl).Register(g) + NewService(ownerToken, authToken, ctrl).Register(g) lis := bufconn.Listen(bufconnSize) go func() { _ = g.Serve(lis) }() conn, err := grpc.NewClient("passthrough:///bufnet", @@ -52,11 +56,25 @@ func newTestEnv(t *testing.T, ownerToken string) *testEnv { return &testEnv{client: corev1.NewCoreServiceClient(conn), ctrl: ctrl} } -func TestTeardownServiceRejectsWrongToken(t *testing.T) { - env := newTestEnv(t, "owner-secret") +// authCtx 返回携带 auth token metadata 的带超时 context。 +func authCtx(t *testing.T, token string) context.Context { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + t.Cleanup(cancel) + return metadata.AppendToOutgoingContext(ctx, AuthTokenMetadataKey, token) +} + +// plainCtx 返回不带 auth metadata 的带超时 context。 +func plainCtx(t *testing.T) context.Context { + t.Helper() ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - defer cancel() - _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: "wrong-token"}) + t.Cleanup(cancel) + return ctx +} + +func TestTeardownServiceRejectsWrongToken(t *testing.T) { + env := newTestEnv(t, testOwnerToken, testAuthToken) + _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: "wrong-token"}) if status.Code(err) != codes.PermissionDenied { t.Fatalf("TeardownService err = %v, want PermissionDenied", err) } @@ -68,10 +86,8 @@ func TestTeardownServiceRejectsWrongToken(t *testing.T) { } func TestTeardownServiceShutsDownServer(t *testing.T) { - env := newTestEnv(t, "owner-secret") - ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - defer cancel() - if _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: "owner-secret"}); err != nil { + env := newTestEnv(t, testOwnerToken, testAuthToken) + if _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: testOwnerToken}); err != nil { t.Fatalf("TeardownService: %v", err) } select { @@ -82,32 +98,13 @@ func TestTeardownServiceShutsDownServer(t *testing.T) { } func TestTeardownServiceRejectsEmptyConfiguredToken(t *testing.T) { - env := newTestEnv(t, "") - ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - defer cancel() - _, err := env.client.TeardownService(ctx, &corev1.TeardownServiceRequest{OwnerToken: ""}) + env := newTestEnv(t, "", testAuthToken) + _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: ""}) if status.Code(err) != codes.PermissionDenied { t.Fatalf("TeardownService err = %v, want PermissionDenied", err) } } -// TestSkeletonRPCsUnimplemented 锁定 PR-1 骨架语义:READY 状态机(PR-2)落地前, -// 除 TeardownService 外的 RPC 一律 UNIMPLEMENTED,不得假成功。 -func TestSkeletonRPCsUnimplemented(t *testing.T) { - env := newTestEnv(t, "owner-secret") - ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - defer cancel() - if _, err := env.client.SpinupService(ctx, &corev1.SpinupServiceRequest{OwnerToken: "owner-secret"}); status.Code(err) != codes.Unimplemented { - t.Fatalf("SpinupService err = %v, want Unimplemented", err) - } - if _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{}); status.Code(err) != codes.Unimplemented { - t.Fatalf("UpsertScalars err = %v, want Unimplemented", err) - } - if _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{}); status.Code(err) != codes.Unimplemented { - t.Fatalf("DeliverRunStart err = %v, want Unimplemented", err) - } -} - func TestControllerShutdownIdempotent(t *testing.T) { g := grpc.NewServer() ctrl := NewController(g, testGrace) @@ -120,4 +117,7 @@ func TestControllerShutdownIdempotent(t *testing.T) { } // 重复读取已关闭的 Done 不应阻塞或 panic <-ctrl.Done() + if st := ctrl.Lifecycle().Get(); st != StateClosed { + t.Fatalf("lifecycle state after shutdown = %v, want StateClosed", st) + } } diff --git a/core/internal/server/session.go b/core/internal/server/session.go new file mode 100644 index 000000000..751909515 --- /dev/null +++ b/core/internal/server/session.go @@ -0,0 +1,96 @@ +package server + +import ( + "crypto/rand" + "encoding/base64" + "sync" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" +) + +// runSessionState 是单个 run 会话的内部状态(不上 proto、不并入 CoreState): +// running → finishStaged(DeliverRunFinish)→ confirmed/释放(ConfirmRunFinish)。 +type runSessionState int32 + +const ( + sessionRunning runSessionState = iota + sessionFinishStaged +) + +// runSession 记录单个 run 会话的路由状态。 +type runSession struct { + mu sync.Mutex + state runSessionState +} + +// finish 幂等标记 finish 已交付。 +func (s *runSession) finish() { + s.mu.Lock() + defer s.mu.Unlock() + s.state = sessionFinishStaged +} + +// coreState 把内部会话状态映射为线上契约 CoreState(run 级数据排空轴)。 +// finish 交付后骨架无数据可排空,直接报 FINISHED,drain 轮询即可判停。 +func (s *runSession) coreState() operationv1.CoreState { + s.mu.Lock() + defer s.mu.Unlock() + if s.state == sessionRunning { + return operationv1.CoreState_CORE_STATE_RUNNING + } + return operationv1.CoreState_CORE_STATE_FINISHED +} + +// sessionRegistry 是并发安全的 run_handle → 会话映射。 +// DeliverRunStart 创建,ConfirmRunFinish 释放;confirm 绝不关闭 gRPC Server。 +type sessionRegistry struct { + mu sync.RWMutex + sessions map[string]*runSession +} + +func newSessionRegistry() *sessionRegistry { + return &sessionRegistry{sessions: make(map[string]*runSession)} +} + +// lookup 校验并返回目标会话;空 handle 为 InvalidArgument, +// 未知或已释放的 handle 为 NotFound。 +func (r *sessionRegistry) lookup(handle string) (*runSession, error) { + if handle == "" { + return nil, status.Error(codes.InvalidArgument, "run_handle must not be empty") + } + r.mu.RLock() + defer r.mu.RUnlock() + s, ok := r.sessions[handle] + if !ok { + return nil, status.Error(codes.NotFound, "unknown or released run_handle") + } + return s, nil +} + +// create 生成随机 opaque handle 并登记会话。 +func (r *sessionRegistry) create() (string, *runSession, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", nil, status.Error(codes.Internal, "generate run handle") + } + handle := base64.RawURLEncoding.EncodeToString(buf) + s := &runSession{} + r.mu.Lock() + defer r.mu.Unlock() + if _, exists := r.sessions[handle]; exists { + // 256-bit 随机碰撞概率可忽略;出现即视为内部错误。 + return "", nil, status.Error(codes.Internal, "run handle collision") + } + r.sessions[handle] = s + return handle, s, nil +} + +// release 摘除会话,仅由 ConfirmRunFinish 成功路径调用。 +func (r *sessionRegistry) release(handle string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.sessions, handle) +} From c9339662a15ca066d034b9040b4539bc543ca583 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Sun, 20 Sep 2026 19:37:02 +0800 Subject: [PATCH 08/15] fix(test): skip non-linux platform --- core/internal/pkg/process/parent_integration_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/internal/pkg/process/parent_integration_test.go b/core/internal/pkg/process/parent_integration_test.go index 63d1f35e9..c92df665b 100644 --- a/core/internal/pkg/process/parent_integration_test.go +++ b/core/internal/pkg/process/parent_integration_test.go @@ -131,6 +131,10 @@ func runMonitoredChildHelper() { // 三级结构与 TestNotifyOnParentExitNotifiesCaller 一致: // 测试进程 -> parent helper(从 LockOSThread 的 goroutine spawn child)-> monitored child。 func TestNotifyOnParentExitSurvivesSpawnThreadExit(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("PDEATHSIG spawn-thread semantics are Linux-specific") + } + switch os.Getenv(parentExitHelperMode) { case "thread-parent": runThreadParentHelper() From bbc259ad8434f60117c88bb011567f303f78b116 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Tue, 22 Sep 2026 17:16:55 +0800 Subject: [PATCH 09/15] fix: fallback loopback --- core/cmd/swanlab-core/main.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index 22381b004..da83bc5f8 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -7,7 +7,8 @@ // --port-filename <路径> listen 成功后原子写入端点回报文件(SDK 启动约定) // // 未传 --listen 时按平台自选端点:POSIX 使用 port-filename 同目录下的 -// core.sock(UDS,目录需已存在),Windows 使用 127.0.0.1 随机回环端口。 +// core.sock(UDS,目录需已存在),listen 失败记录 warning 后回退 +// 127.0.0.1 随机回环端口;Windows 直接使用随机回环端口。 // --port-filename 与 --owner-token-file 成对出现,owner token 是唯一允许 // 触发服务级关闭的凭证,通过私有文件传入,不得出现在命令行或日志中。 // @@ -213,15 +214,21 @@ func run(args []string) int { return exitCode } -// openEndpoint 创建监听器。显式 --listen 优先(手动调试);否则按平台自选: -// POSIX 使用 port-filename 同目录下的 UDS,Windows 使用随机回环端口。 +// openEndpoint 创建监听器。显式 --listen 优先(手动调试,失败不回退); +// 否则按平台自选:POSIX 先尝试 port-filename 同目录下的 UDS,失败记录 +// warning 后回退随机回环 TCP;Windows 直接使用随机回环端口。 func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { if listenAddr == "" { if runtime.GOOS == "windows" { return net.Listen("tcp", loopbackAddr) } sockPath := filepath.Join(filepath.Dir(portFilename), coreSocketName) - return net.Listen("unix", sockPath) + ln, err := net.Listen("unix", sockPath) + if err != nil { + console.Warningf("unix listen on %s failed: %v; falling back to loopback tcp %s", sockPath, err, loopbackAddr) + return net.Listen("tcp", loopbackAddr) + } + return ln, nil } scheme, rest, ok := strings.Cut(listenAddr, "://") if !ok { From 4f70a6b8501420b58a3cee500abb716b82c61af5 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Wed, 23 Sep 2026 15:11:59 +0800 Subject: [PATCH 10/15] chore: rename auth token --- core/internal/pkg/portinfo/portinfo.go | 21 ++++---- core/internal/pkg/portinfo/portinfo_test.go | 53 +++++++++---------- core/internal/server/auth.go | 4 +- swanlab/sdk/internal/pkg/fork/__init__.py | 57 ++++++++++++++++----- 4 files changed, 83 insertions(+), 52 deletions(-) diff --git a/core/internal/pkg/portinfo/portinfo.go b/core/internal/pkg/portinfo/portinfo.go index 5d009a256..d7b17c235 100644 --- a/core/internal/pkg/portinfo/portinfo.go +++ b/core/internal/pkg/portinfo/portinfo.go @@ -7,7 +7,7 @@ // protocol=1 // unix=/short/private/runtime/core.sock (POSIX 平台) // sock=12345 (Windows 平台,替代 unix 行) -// auth= +// auth_token= // EOF // // 写入通过同目录临时文件 + fsync + chmod(0600) + rename 原子提交,读者只会看到 @@ -45,10 +45,10 @@ const ( // 字段名约定,解析与序列化共用。 const ( - keyProtocol = "protocol" - keyUnix = "unix" - keySock = "sock" - keyAuth = "auth" + keyProtocol = "protocol" + keyUnix = "unix" + keySock = "sock" + keyAuthToken = "auth_token" ) // Info 是 port-file 的结构化内容,UnixPath 与 SockPort 二选一。 @@ -81,7 +81,7 @@ func Marshal(info *Info) ([]byte, error) { } else { fmt.Fprintf(&b, "%s=%d\n", keySock, info.SockPort) } - fmt.Fprintf(&b, "%s=%s\n", keyAuth, info.AuthToken) + fmt.Fprintf(&b, "%s=%s\n", keyAuthToken, info.AuthToken) b.WriteString(eofMarker + "\n") return []byte(b.String()), nil } @@ -109,9 +109,6 @@ func WriteFile(path string, info *Info) error { _ = tmp.Close() return fmt.Errorf("write temp port-file: %w", err) } - // fsync 尽力而为:部分网络文件系统(如 smbfs)不支持 fsync 而返回 EINVAL。 - // port-file 为短命文件且由同机读者在秒级内读取,可见性一致性由 rename 原子性保证, - // fsync 仅作为崩溃防护,失败不阻塞端点回报。 _ = tmp.Sync() if err := tmp.Close(); err != nil { return fmt.Errorf("close temp port-file: %w", err) @@ -186,7 +183,7 @@ func Parse(data []byte) (Info, error) { return Info{}, err } info.SockPort = port - case keyAuth: + case keyAuthToken: if err := validateAuthToken(value); err != nil { return Info{}, err } @@ -200,8 +197,8 @@ func Parse(data []byte) (Info, error) { if !seen[keyProtocol] { return Info{}, fmt.Errorf("port-file missing %q field", keyProtocol) } - if !seen[keyAuth] { - return Info{}, fmt.Errorf("port-file missing %q field", keyAuth) + if !seen[keyAuthToken] { + return Info{}, fmt.Errorf("port-file missing %q field", keyAuthToken) } if seen[keyUnix] == seen[keySock] { return Info{}, fmt.Errorf("port-file must contain exactly one of %q or %q", keyUnix, keySock) diff --git a/core/internal/pkg/portinfo/portinfo_test.go b/core/internal/pkg/portinfo/portinfo_test.go index 3586a592e..04d388647 100644 --- a/core/internal/pkg/portinfo/portinfo_test.go +++ b/core/internal/pkg/portinfo/portinfo_test.go @@ -16,7 +16,7 @@ func testToken() string { } func validUnixContent() string { - return "protocol=1\nunix=/tmp/swanlab/core.sock\nauth=" + testToken() + "\nEOF\n" + return "protocol=1\nunix=/tmp/swanlab/core.sock\nauth_token=" + testToken() + "\nEOF\n" } func TestMarshalUnixFormat(t *testing.T) { @@ -34,7 +34,7 @@ func TestMarshalSockFormat(t *testing.T) { if err != nil { t.Fatalf("Marshal: %v", err) } - want := "protocol=1\nsock=12345\nauth=" + testToken() + "\nEOF\n" + want := "protocol=1\nsock=12345\nauth_token=" + testToken() + "\nEOF\n" if got := string(data); got != want { t.Fatalf("Marshal output mismatch:\n got: %q\nwant: %q", got, want) } @@ -164,12 +164,12 @@ func TestParseAcceptsValidVariants(t *testing.T) { t.Fatalf("parse without trailing newline: %v", err) } // 键值行顺序不影响解析 - reordered := "auth=" + testToken() + "\nunix=/tmp/swanlab/core.sock\nprotocol=1\nEOF\n" + reordered := "auth_token=" + testToken() + "\nunix=/tmp/swanlab/core.sock\nprotocol=1\nEOF\n" if _, err := Parse([]byte(reordered)); err != nil { t.Fatalf("parse reordered lines: %v", err) } // sock 端点(Windows 形态) - sockContent := "protocol=1\nsock=12345\nauth=" + testToken() + "\nEOF\n" + sockContent := "protocol=1\nsock=12345\nauth_token=" + testToken() + "\nEOF\n" info, err := Parse([]byte(sockContent)) if err != nil { t.Fatalf("parse sock content: %v", err) @@ -181,30 +181,31 @@ func TestParseAcceptsValidVariants(t *testing.T) { func TestParseRejectsMalformed(t *testing.T) { cases := map[string]string{ - "missing EOF": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\n", - "EOF not own line": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "EOF\n", + "missing EOF": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\n", + "EOF not own line": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "EOF\n", "content after EOF": validUnixContent() + "extra\n", "torn write": "protocol=1\nunix=/tmp/a.sock\n", "empty body": "EOF\n", - "empty line": "protocol=1\n\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "not key value": "protocol=1\nunix=/tmp/a.sock\nauth\nEOF\n", - "unknown key": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nextra=1\nEOF\n", - "duplicate key": "protocol=1\nprotocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "unknown protocol": "protocol=2\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "non numeric protocol": "protocol=abc\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "missing protocol": "unix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "missing auth": "protocol=1\nunix=/tmp/a.sock\nEOF\n", - "missing endpoint": "protocol=1\nauth=" + testToken() + "\nEOF\n", - "both endpoints": "protocol=1\nunix=/tmp/a.sock\nsock=12345\nauth=" + testToken() + "\nEOF\n", - "relative unix path": "protocol=1\nunix=tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "oversize unix path": "protocol=1\nunix=/" + strings.Repeat("a", maxUnixPathLen) + "\nauth=" + testToken() + "\nEOF\n", - "port zero": "protocol=1\nsock=0\nauth=" + testToken() + "\nEOF\n", - "port range": "protocol=1\nsock=65536\nauth=" + testToken() + "\nEOF\n", - "port leading zero": "protocol=1\nsock=01234\nauth=" + testToken() + "\nEOF\n", - "port not digits": "protocol=1\nsock=12a45\nauth=" + testToken() + "\nEOF\n", - "token too short": "protocol=1\nunix=/tmp/a.sock\nauth=QiQi\nEOF\n", - "token std alphabet": "protocol=1\nunix=/tmp/a.sock\nauth=" + strings.Repeat("+", 43) + "\nEOF\n", - "token padded": "protocol=1\nunix=/tmp/a.sock\nauth=" + strings.TrimSuffix(testToken(), "i") + "i=\nEOF\n", + "empty line": "protocol=1\n\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "not key value": "protocol=1\nunix=/tmp/a.sock\nauth_token\nEOF\n", + "unknown key": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nextra=1\nEOF\n", + "duplicate key": "protocol=1\nprotocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "unknown protocol": "protocol=2\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "non numeric protocol": "protocol=abc\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "missing protocol": "unix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "missing auth_token": "protocol=1\nunix=/tmp/a.sock\nEOF\n", + "legacy auth key": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", + "missing endpoint": "protocol=1\nauth_token=" + testToken() + "\nEOF\n", + "both endpoints": "protocol=1\nunix=/tmp/a.sock\nsock=12345\nauth_token=" + testToken() + "\nEOF\n", + "relative unix path": "protocol=1\nunix=tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", + "oversize unix path": "protocol=1\nunix=/" + strings.Repeat("a", maxUnixPathLen) + "\nauth_token=" + testToken() + "\nEOF\n", + "port zero": "protocol=1\nsock=0\nauth_token=" + testToken() + "\nEOF\n", + "port range": "protocol=1\nsock=65536\nauth_token=" + testToken() + "\nEOF\n", + "port leading zero": "protocol=1\nsock=01234\nauth_token=" + testToken() + "\nEOF\n", + "port not digits": "protocol=1\nsock=12a45\nauth_token=" + testToken() + "\nEOF\n", + "token too short": "protocol=1\nunix=/tmp/a.sock\nauth_token=QiQi\nEOF\n", + "token std alphabet": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + strings.Repeat("+", 43) + "\nEOF\n", + "token padded": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + strings.TrimSuffix(testToken(), "i") + "i=\nEOF\n", } for name, content := range cases { if _, err := Parse([]byte(content)); err == nil { @@ -214,7 +215,7 @@ func TestParseRejectsMalformed(t *testing.T) { } func TestParseRejectsOversizeContent(t *testing.T) { - content := "protocol=1\nunix=/tmp/" + strings.Repeat("a", MaxFileSize) + "\nauth=" + testToken() + "\nEOF\n" + content := "protocol=1\nunix=/tmp/" + strings.Repeat("a", MaxFileSize) + "\nauth_token=" + testToken() + "\nEOF\n" if _, err := Parse([]byte(content)); err == nil { t.Fatal("Parse unexpectedly succeeded for oversize content") } diff --git a/core/internal/server/auth.go b/core/internal/server/auth.go index 6043310fc..4642565a6 100644 --- a/core/internal/server/auth.go +++ b/core/internal/server/auth.go @@ -11,8 +11,8 @@ import ( ) // AuthTokenMetadataKey 是每个 RPC 必须携带的 auth token metadata 键, -// token 值来自 port-file 的 auth 字段。 -const AuthTokenMetadataKey = "x-swanlab-core-auth-token" +// token 值来自 port-file 的 auth_token 字段。 +const AuthTokenMetadataKey = "auth_token" // UnaryAuthInterceptor 校验每个 unary RPC 的 auth token,使用常量时间比较, // 拒绝缺失或错误的 token。expectedToken 为空表示未启用鉴权,仅限 --listen diff --git a/swanlab/sdk/internal/pkg/fork/__init__.py b/swanlab/sdk/internal/pkg/fork/__init__.py index da04307fd..b05149270 100644 --- a/swanlab/sdk/internal/pkg/fork/__init__.py +++ b/swanlab/sdk/internal/pkg/fork/__init__.py @@ -13,7 +13,8 @@ 设计原则: - 回调注册表:各组件通过 ``register()`` 注册自己的 fork 清理逻辑,而非内联 ``register_at_fork`` 调用 -- 显式优于隐式:fork 后的清理通过回调显式触发,而非依赖属性计算的副作用 +- 生命周期安全:bound method 回调以弱引用存储,注册不会延长实例生命周期; + 回调内禁止获取实例级锁(fork 时该锁可能由不会存在于子进程的线程持有) - 面向未来:当 ``swanlab-core`` 上线后,可注册重连回调替代简单的 ``clear_run()`` 使用示例:: @@ -34,16 +35,38 @@ fork.unregister(some_cleanup) """ +import inspect import os import threading -from typing import Callable, List +import weakref +from typing import Callable, List, Optional -# 回调列表:fork 后在子进程中执行的清理函数 +# 回调注册表:fork 后在子进程中执行的清理函数。 +# bound method 以 WeakMethod 存储:注册不得延长实例生命周期, +# 否则实例上的 weakref.finalize 等最后保险永远不会触发。 +# 模块级函数与 lambda 保持强引用(其生命周期本就不随实例结束)。 # 使用锁保护,因为 register() 可能在多线程环境中被调用 -_callbacks: List[Callable[[], None]] = [] +_callbacks: List[object] = [] _lock = threading.Lock() +def _make_entry(callback: Callable[[], None]) -> object: + if inspect.ismethod(callback): + return weakref.WeakMethod(callback) + return callback + + +def _resolve_entry(entry: object) -> Optional[Callable[[], None]]: + if isinstance(entry, weakref.WeakMethod): + return entry() + return entry # type: ignore[return-value] + + +def _prune_dead() -> None: + """清除已死亡的弱引用条目。需持锁调用。""" + _callbacks[:] = [e for e in _callbacks if _resolve_entry(e) is not None] + + def current_pid() -> int: """获取当前进程 PID。 @@ -72,11 +95,17 @@ def register(callback: Callable[[], None]) -> None: 回调在 ``os.register_at_fork(after_in_child=...)`` 触发时执行, 即 fork 后子进程的第一时间。回调应尽量轻量,避免阻塞或抛出异常。 + 特别注意:回调内不得获取可能被其他线程持有、而该线程不会存在于 + 子进程中的锁,否则子进程会在 fork 返回前永久阻塞。 + + bound method 以弱引用存储:实例被回收后回调自动失效;注册方仍应 + 在生命周期结束时调用 ``unregister`` 即时移除条目。 :param callback: 无参回调函数,在 fork 后的子进程中执行 """ with _lock: - _callbacks.append(callback) + _prune_dead() + _callbacks.append(_make_entry(callback)) def unregister(callback: Callable[[], None]) -> None: @@ -88,10 +117,11 @@ def unregister(callback: Callable[[], None]) -> None: :param callback: 之前通过 ``register()`` 注册的回调函数 """ with _lock: - try: - _callbacks.remove(callback) - except ValueError: - pass + _prune_dead() + for index, entry in enumerate(_callbacks): + if _resolve_entry(entry) == callback: + del _callbacks[index] + return def _before_fork() -> None: @@ -114,15 +144,18 @@ def _after_in_child() -> None: """``os.register_at_fork(after_in_child=...)`` 的处理器。 在 fork 后的子进程中执行所有已注册的回调,然后重置锁。 - 回调执行顺序与注册顺序一致(FIFO)。 + 回调执行顺序与注册顺序一致(FIFO);弱引用已死亡的条目被跳过 + (父进程侧的注册表不因 fork 而改变)。 因为 _before_fork 已获取锁,此处锁处于持有状态。 直接重置为新锁,避免继承父进程的锁状态。 """ # _before_fork 已获取锁,此时 _callbacks 一定处于一致状态 try: - for cb in list(_callbacks): - cb() + for entry in list(_callbacks): + cb = _resolve_entry(entry) + if cb is not None: + cb() finally: global _lock _lock = threading.Lock() From e5fe9d4550403b341d133971be5604f6380197bb Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Wed, 23 Sep 2026 16:19:43 +0800 Subject: [PATCH 11/15] fix: safe clean port file --- core/cmd/swanlab-core/main.go | 42 ++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index da83bc5f8..ae871410c 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -104,11 +104,12 @@ func run(args []string) int { // 自建资源记录,退出时只清理自己创建的部分。 var socketPath string + authToken := "" // 退出时凭它确认 port-file 仍属本实例 wrotePortFile := false defer func() { cleanupSocket(socketPath) if wrotePortFile { - _ = os.Remove(*portFilename) + cleanupPortFile(*portFilename, authToken) } }() @@ -143,7 +144,6 @@ func run(args []string) int { // auth token 先于 gRPC server 创建生成:interceptor 与 port-file 使用同一值; // 仅 --listen 手动调试(无 port-file)时不生成,鉴权随之关闭。 - authToken := "" if *portFilename != "" { var err2 error authToken, err2 = portinfo.NewAuthToken() @@ -214,7 +214,7 @@ func run(args []string) int { return exitCode } -// openEndpoint 创建监听器。显式 --listen 优先(手动调试,失败不回退); +// openEndpoint 创建监听器。显式 --listen 优先(手动调试,失败不回退,tcp:// 仅允许回环); // 否则按平台自选:POSIX 先尝试 port-filename 同目录下的 UDS,失败记录 // warning 后回退随机回环 TCP;Windows 直接使用随机回环端口。 func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { @@ -241,6 +241,9 @@ func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { } return net.Listen("unix", rest) case "tcp": + if err := requireLoopbackHost(rest); err != nil { + return nil, err + } return net.Listen("tcp", rest) default: return nil, fmt.Errorf("unsupported listen scheme %q (only unix:// or tcp://)", scheme) @@ -276,6 +279,39 @@ func cleanupSocket(path string) { _ = os.Remove(path) } +// cleanupPortFile 删除 port-file 前先确认内容仍属本实例(auth token 匹配)。 +// port-file 是服务级单例发现文件:同一路径被后启动实例覆盖后,本实例退出 +// 不得误删他者文件;缺失、损坏或 token 不匹配一律不删。 +func cleanupPortFile(path, authToken string) { + if path == "" || authToken == "" { + return + } + info, err := portinfo.ParseFile(path) + if err != nil { + return + } + if info.AuthToken != authToken { + return + } + _ = os.Remove(path) +} + +// requireLoopbackHost 限制显式 tcp:// 监听地址为回环。手动调试模式鉴权关闭, +// 绑定非回环地址会把无鉴权服务暴露到网络,故仅放行 127.0.0.1/::1/localhost。 +func requireLoopbackHost(addr string) error { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return fmt.Errorf("parse tcp listen address %q: %w", addr, err) + } + if host == "localhost" { + return nil + } + if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() { + return nil + } + return fmt.Errorf("tcp listen address %q is not loopback; use 127.0.0.1 or ::1 (manual debug runs without auth)", addr) +} + // envInt 解析整型环境变量,缺失或非法时返回 0。 func envInt(name string) int { v, err := strconv.Atoi(strings.TrimSpace(os.Getenv(name))) From f4ae2c975519ab2a80a8e2d54d65761c8b171300 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Wed, 23 Sep 2026 20:12:56 +0800 Subject: [PATCH 12/15] refactor: remove redundant token --- core/cmd/swanlab-core/main.go | 122 ++++++++----------- core/internal/pkg/portinfo/portinfo.go | 104 ++++++++-------- core/internal/pkg/portinfo/portinfo_test.go | 124 ++++++++------------ core/internal/server/auth.go | 46 -------- core/internal/server/lifecycle_test.go | 81 ++++--------- core/internal/server/server.go | 63 ++++------ core/internal/server/server_test.go | 56 +++------ 7 files changed, 207 insertions(+), 389 deletions(-) delete mode 100644 core/internal/server/auth.go diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index ae871410c..de64b472a 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -1,5 +1,9 @@ // Command swanlab-core 是 SwanLab Go core 的进程入口。 // +// 启动约定(Owner Mode): +// +// swanlab-core --port-filename /core.port --parent-pid +// // 端点约定: // // --listen unix:///path/to/uds Linux/macOS 进程内通信(手动调试入口) @@ -8,13 +12,19 @@ // // 未传 --listen 时按平台自选端点:POSIX 使用 port-filename 同目录下的 // core.sock(UDS,目录需已存在),listen 失败记录 warning 后回退 -// 127.0.0.1 随机回环端口;Windows 直接使用随机回环端口。 -// --port-filename 与 --owner-token-file 成对出现,owner token 是唯一允许 -// 触发服务级关闭的凭证,通过私有文件传入,不得出现在命令行或日志中。 +// 127.0.0.1 随机回环端口;Windows 使用随机回环端口。 +// +// 信任模型:Owner Mode 不使用应用层 token,安全边界由本地 transport 与 +// 文件系统权限承担:POSIX 使用 UDS(socket 文件位于 owner-only 私有 +// runtime 目录),port-file 以 0600 权限原子发布;TCP 回退对同机同用户 +// 进程开放,不提供应用层访问控制。 +// +// --detach 与 --idle-timeout 为 detached 模式预留:detached 未实现, +// 传入报用法错误退出。 // // 生命周期:Teardown RPC、SIGINT/SIGTERM、父进程退出(process 包监控)或 -// Serve 异常统一汇入 service controller 的关闭路径(GracefulStop → 超时 -// 强制 Stop);退出时只清理自己创建的 socket 文件与 port-file。 +// Serve 异常汇入 service controller 的关闭路径(GracefulStop → 超时强制 +// Stop);退出时清理自己创建的 socket 文件与 port-file。 package main import ( @@ -41,7 +51,7 @@ import ( ) // version 与 commit 由构建管线通过 -ldflags -X 注入(见 core/hatch.py), -// 缺省值仅供本地 go run / go build 使用。 +// 缺省值供本地 go run / go build 使用。 var ( version = "dev" commit = "unknown" @@ -61,10 +71,9 @@ const ( // 自选端点与收尾参数。 const ( - coreSocketName = "core.sock" - loopbackAddr = "127.0.0.1:0" - shutdownGrace = 10 * time.Second - secretFileMaxSize = 4096 + coreSocketName = "core.sock" + loopbackAddr = "127.0.0.1:0" + shutdownGrace = 10 * time.Second ) func main() { @@ -78,10 +87,12 @@ func run(args []string) int { "listen endpoint, unix:// or tcp://; auto-selected per platform when unset") portFilename := fs.String("port-filename", "", "endpoint report file; atomically written once listen succeeds, for callers to poll") - ownerTokenFile := fs.String("owner-token-file", "", - "owner token file, held only by the service owner; sole credential for service-level teardown") parentPID := fs.Int("parent-pid", envInt(envParentPID), "expected parent PID; core exits when the parent exits, defaults to the actual parent at startup") + detach := fs.Bool("detach", false, + "detached mode (not implemented in this build)") + idleTimeout := fs.Duration("idle-timeout", 0, + "detached idle timeout (not implemented in this build)") if err := fs.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { return 0 @@ -93,33 +104,26 @@ func run(args []string) int { fmt.Printf("swanlab-core %s (commit %s)\n", version, commit) return 0 } - if *listenAddr == "" && *portFilename == "" { - console.Error("no listen endpoint: pass --listen (manual debug) or --port-filename (SDK startup convention)") + if *detach || *idleTimeout != 0 { + console.Error("--detach/--idle-timeout: detached mode is not implemented in this build") return exitUsageError } - if (*portFilename != "") != (*ownerTokenFile != "") { - console.Error("--port-filename and --owner-token-file must be provided together") + if *listenAddr == "" && *portFilename == "" { + console.Error("no listen endpoint: pass --listen (manual debug) or --port-filename (SDK startup convention)") return exitUsageError } - // 自建资源记录,退出时只清理自己创建的部分。 + // 自建资源记录,退出时清理自己创建的部分。 var socketPath string - authToken := "" // 退出时凭它确认 port-file 仍属本实例 + selfPID := os.Getpid() // 退出时凭它确认 port-file 属本实例 wrotePortFile := false defer func() { cleanupSocket(socketPath) if wrotePortFile { - cleanupPortFile(*portFilename, authToken) + cleanupPortFile(*portFilename, selfPID) } }() - // owner token 先于任何资源创建读取,尽早失败。 - ownerToken, err := readOwnerToken(*ownerTokenFile) - if err != nil { - console.Error("failed to read owner token:", err) - return exitRunError - } - ln, err := openEndpoint(*listenAddr, *portFilename) if err != nil { console.Error("listen failed:", err) @@ -130,8 +134,8 @@ func run(args []string) int { socketPath = addr.Name } - // 父进程监控:显式传入的 PID 优先(启动约定),未传时回退为监控启动 - // 瞬间的实际父进程(本地终端运行场景)。监控建立失败按约定终止启动。 + // 父进程监控:显式传入的 PID 生效(启动约定);未传时监控启动瞬间的 + // 实际父进程(本地终端运行场景)。监控建立失败终止启动。 pid := *parentPID if pid <= 0 { pid = os.Getppid() @@ -142,24 +146,13 @@ func run(args []string) int { return exitRunError } - // auth token 先于 gRPC server 创建生成:interceptor 与 port-file 使用同一值; - // 仅 --listen 手动调试(无 port-file)时不生成,鉴权随之关闭。 - if *portFilename != "" { - var err2 error - authToken, err2 = portinfo.NewAuthToken() - if err2 != nil { - console.Error("failed to generate auth token:", err2) - return exitRunError - } - } - - grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(server.UnaryAuthInterceptor(authToken))) + grpcServer := grpc.NewServer() ctrl := server.NewController(grpcServer, shutdownGrace) - server.NewService(ownerToken, authToken, ctrl).Register(grpcServer) + server.NewService(ctrl).Register(grpcServer) - // listen 与 server 初始化均成功后才写 port-file。 + // listen 与 server 初始化成功后写 port-file。 if *portFilename != "" { - info := portinfo.Info{Protocol: portinfo.ProtocolVersion, AuthToken: authToken} + info := portinfo.Info{Protocol: portinfo.ProtocolVersion, PID: selfPID} switch addr := ln.Addr().(type) { case *net.UnixAddr: info.UnixPath = addr.Name @@ -214,9 +207,9 @@ func run(args []string) int { return exitCode } -// openEndpoint 创建监听器。显式 --listen 优先(手动调试,失败不回退,tcp:// 仅允许回环); -// 否则按平台自选:POSIX 先尝试 port-filename 同目录下的 UDS,失败记录 -// warning 后回退随机回环 TCP;Windows 直接使用随机回环端口。 +// openEndpoint 创建监听器。显式 --listen 生效(手动调试,失败不回退, +// tcp:// 限定回环);未传时按平台自选:POSIX 尝试 port-filename 同目录 +// 下的 UDS,失败记录 warning 后回退随机回环 TCP;Windows 使用随机回环端口。 func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { if listenAddr == "" { if runtime.GOOS == "windows" { @@ -250,26 +243,6 @@ func openEndpoint(listenAddr, portFilename string) (net.Listener, error) { } } -// readOwnerToken 读取 owner token 文件;路径为空返回空串。内容去除首尾空白, -// 拒绝空文件与超长文件,token 值不进入日志。 -func readOwnerToken(path string) (string, error) { - if path == "" { - return "", nil - } - data, err := os.ReadFile(path) - if err != nil { - return "", fmt.Errorf("read %s: %w", path, err) - } - if len(data) > secretFileMaxSize { - return "", fmt.Errorf("owner token file exceeds %d bytes: %s", secretFileMaxSize, path) - } - token := strings.TrimSpace(string(data)) - if token == "" { - return "", fmt.Errorf("owner token file is empty: %s", path) - } - return token, nil -} - // cleanupSocket 删除自己创建的 UDS socket 文件;Linux 抽象 socket(@ 前缀) // 不占文件系统,无需清理。 func cleanupSocket(path string) { @@ -279,25 +252,24 @@ func cleanupSocket(path string) { _ = os.Remove(path) } -// cleanupPortFile 删除 port-file 前先确认内容仍属本实例(auth token 匹配)。 -// port-file 是服务级单例发现文件:同一路径被后启动实例覆盖后,本实例退出 -// 不得误删他者文件;缺失、损坏或 token 不匹配一律不删。 -func cleanupPortFile(path, authToken string) { - if path == "" || authToken == "" { +// cleanupPortFile 在 pid 匹配时删除 port-file。同一路径被后启动实例 +// 覆盖后,本实例退出不得误删他者文件;文件缺失、损坏或 pid 不匹配时不删。 +func cleanupPortFile(path string, pid int) { + if path == "" || pid <= 0 { return } info, err := portinfo.ParseFile(path) if err != nil { return } - if info.AuthToken != authToken { + if info.PID != pid { return } _ = os.Remove(path) } -// requireLoopbackHost 限制显式 tcp:// 监听地址为回环。手动调试模式鉴权关闭, -// 绑定非回环地址会把无鉴权服务暴露到网络,故仅放行 127.0.0.1/::1/localhost。 +// requireLoopbackHost 限制显式 tcp:// 监听地址为回环,避免服务暴露到 +// 网络;放行 127.0.0.1/::1/localhost。 func requireLoopbackHost(addr string) error { host, _, err := net.SplitHostPort(addr) if err != nil { @@ -309,7 +281,7 @@ func requireLoopbackHost(addr string) error { if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() { return nil } - return fmt.Errorf("tcp listen address %q is not loopback; use 127.0.0.1 or ::1 (manual debug runs without auth)", addr) + return fmt.Errorf("tcp listen address %q is not loopback; use 127.0.0.1 or ::1", addr) } // envInt 解析整型环境变量,缺失或非法时返回 0。 diff --git a/core/internal/pkg/portinfo/portinfo.go b/core/internal/pkg/portinfo/portinfo.go index d7b17c235..29da155e4 100644 --- a/core/internal/pkg/portinfo/portinfo.go +++ b/core/internal/pkg/portinfo/portinfo.go @@ -1,23 +1,21 @@ -// Package portinfo 实现 swanlab-core 端口信息文件(port-file)的严格读写。 +// Package portinfo 实现 swanlab-core 端口信息文件(port-file)的读写。 // -// port-file 是 Go core 服务在 listen 成功后向 Python SDK 回报监听端点的约定文件, -// 同时携带 RPC 鉴权所需的 auth token。文件格式(v1)为若干行 key=value 文本, +// port-file 是 core 服务在 listen 成功后向调用方回报监听端点的约定文件, +// 承担临时 endpoint discovery。文件格式(v1)为若干行 key=value 文本, // 以独立的 EOF 行结尾: // // protocol=1 -// unix=/short/private/runtime/core.sock (POSIX 平台) -// sock=12345 (Windows 平台,替代 unix 行) -// auth_token= +// pid= (诊断与 stale 检查辅助) +// unix=/short/private/runtime/core.sock (POSIX 平台) +// sock=12345 (Windows 或 UDS 回退,替代 unix 行) // EOF // -// 写入通过同目录临时文件 + fsync + chmod(0600) + rename 原子提交,读者只会看到 -// 完整文件;解析对重复 key、未知协议版本、缺失字段、非法端口、非 EOF 结尾和 -// 超长内容一律拒绝。auth token 属于敏感信息,任何错误消息中不得包含其值。 +// 写入经同目录临时文件 + fsync + chmod(0600) + rename 原子提交;解析拒绝 +// 重复 key、未知 key、未知协议版本、缺失字段、非法端口或 pid、非 EOF 结尾 +// 和超长内容。 package portinfo import ( - "crypto/rand" - "encoding/base64" "errors" "fmt" "io" @@ -33,61 +31,51 @@ const ( ProtocolVersion = 1 // MaxFileSize 是 port-file 内容长度上限,超出视为损坏或恶意构造。 MaxFileSize = 4096 - // authTokenBytes 是 auth token 解码后的字节数(256-bit)。 - authTokenBytes = 32 + // maxPID 是 pid 字段的取值上限。 + maxPID = 1<<31 - 1 // maxUnixPathLen 限定 unix 端点路径长度,实际可用长度还受 sun_path 限制。 maxUnixPathLen = 256 // filePerm 是 port-file 的 owner-only 权限。 filePerm = 0o600 - // eofMarker 是文件结尾标记,必须独占一行。 + // eofMarker 是文件结尾标记,独占一行。 eofMarker = "EOF" ) // 字段名约定,解析与序列化共用。 const ( - keyProtocol = "protocol" - keyUnix = "unix" - keySock = "sock" - keyAuthToken = "auth_token" + keyProtocol = "protocol" + keyUnix = "unix" + keySock = "sock" + keyPID = "pid" ) // Info 是 port-file 的结构化内容,UnixPath 与 SockPort 二选一。 type Info struct { - Protocol int - UnixPath string - SockPort int - AuthToken string + Protocol int + UnixPath string + SockPort int + PID int } -// NewAuthToken 生成 base64url 编码的 256-bit 随机 token, -// 用于写入 port-file 的 auth 字段。 -func NewAuthToken() (string, error) { - buf := make([]byte, authTokenBytes) - if _, err := rand.Read(buf); err != nil { - return "", fmt.Errorf("generate auth token: %w", err) - } - return base64.RawURLEncoding.EncodeToString(buf), nil -} - -// Marshal 将 Info 序列化为 v1 格式字节串,序列化前完成全部校验。 +// Marshal 将 Info 序列化为 v1 格式字节串,序列化前完成校验。 func Marshal(info *Info) ([]byte, error) { if err := validate(info); err != nil { return nil, err } var b strings.Builder fmt.Fprintf(&b, "%s=%d\n", keyProtocol, info.Protocol) + fmt.Fprintf(&b, "%s=%d\n", keyPID, info.PID) if info.UnixPath != "" { fmt.Fprintf(&b, "%s=%s\n", keyUnix, info.UnixPath) } else { fmt.Fprintf(&b, "%s=%d\n", keySock, info.SockPort) } - fmt.Fprintf(&b, "%s=%s\n", keyAuthToken, info.AuthToken) b.WriteString(eofMarker + "\n") return []byte(b.String()), nil } // WriteFile 原子写入 port-file:先写同目录临时文件,fsync、chmod 后 rename 覆盖目标。 -// 任一步失败都不会破坏既有目标文件,临时文件会被清理。 +// 任一步失败不破坏既有目标文件,临时文件会被清理。 func WriteFile(path string, info *Info) error { data, err := Marshal(info) if err != nil { @@ -124,7 +112,7 @@ func WriteFile(path string, info *Info) error { return nil } -// ParseFile 读取并严格解析 port-file。文件超过 MaxFileSize 时按损坏处理。 +// ParseFile 读取并解析 port-file。文件超过 MaxFileSize 时按损坏处理。 func ParseFile(path string) (Info, error) { f, err := os.Open(path) if err != nil { @@ -138,8 +126,8 @@ func ParseFile(path string) (Info, error) { return Parse(data) } -// Parse 严格解析 v1 格式内容。键值行顺序不限,但每个必要字段恰好出现一次, -// 必须以独立 EOF 行结尾(末尾换行可选)。 +// Parse 解析 v1 格式内容。键值行顺序不限,每个字段出现一次, +// 以独立 EOF 行结尾(末尾换行可选)。 func Parse(data []byte) (Info, error) { if len(data) > MaxFileSize { return Info{}, fmt.Errorf("port-file exceeds %d bytes", MaxFileSize) @@ -154,7 +142,7 @@ func Parse(data []byte) (Info, error) { } var info Info - seen := make(map[string]bool, 3) + seen := make(map[string]bool, 4) for _, line := range strings.Split(body, "\n") { if line == "" { return Info{}, errors.New("port-file contains empty line") @@ -183,11 +171,12 @@ func Parse(data []byte) (Info, error) { return Info{}, err } info.SockPort = port - case keyAuthToken: - if err := validateAuthToken(value); err != nil { + case keyPID: + pid, err := parsePID(value) + if err != nil { return Info{}, err } - info.AuthToken = value + info.PID = pid default: return Info{}, fmt.Errorf("port-file contains unknown key %q", key) } @@ -197,8 +186,8 @@ func Parse(data []byte) (Info, error) { if !seen[keyProtocol] { return Info{}, fmt.Errorf("port-file missing %q field", keyProtocol) } - if !seen[keyAuthToken] { - return Info{}, fmt.Errorf("port-file missing %q field", keyAuthToken) + if !seen[keyPID] { + return Info{}, fmt.Errorf("port-file missing %q field", keyPID) } if seen[keyUnix] == seen[keySock] { return Info{}, fmt.Errorf("port-file must contain exactly one of %q or %q", keyUnix, keySock) @@ -206,11 +195,14 @@ func Parse(data []byte) (Info, error) { return info, nil } -// validate 校验 Info 的全部字段约束。 +// validate 校验 Info 的字段约束。 func validate(info *Info) error { if info.Protocol != ProtocolVersion { return fmt.Errorf("unsupported port-file protocol %d", info.Protocol) } + if info.PID < 1 || info.PID > maxPID { + return fmt.Errorf("pid %d out of range", info.PID) + } hasUnix := info.UnixPath != "" hasSock := info.SockPort != 0 if hasUnix == hasSock { @@ -223,20 +215,22 @@ func validate(info *Info) error { } else if info.SockPort < 1 || info.SockPort > 65535 { return fmt.Errorf("sock port %d out of range", info.SockPort) } - return validateAuthToken(info.AuthToken) + return nil } -// validateAuthToken 校验 token 是 base64url 编码且解码后恰好 256-bit。 -// 错误消息不回显 token 值,避免敏感信息泄露。 -func validateAuthToken(token string) error { - raw, err := base64.RawURLEncoding.Strict().DecodeString(token) - if err != nil || len(raw) != authTokenBytes { - return errors.New("auth token must be base64url-encoded 256-bit value") +// parsePID 解析十进制 pid:数字、无前导零、范围 1-maxPID。 +func parsePID(value string) (int, error) { + if value == "" || strings.HasPrefix(value, "0") || !isDigits(value) { + return 0, fmt.Errorf("invalid pid %q", value) } - return nil + pid, err := strconv.Atoi(value) + if err != nil || pid < 1 || pid > maxPID { + return 0, fmt.Errorf("invalid pid %q", value) + } + return pid, nil } -// parsePort 严格解析十进制端口号:仅数字、无前导零、范围 1-65535。 +// parsePort 解析十进制端口号:数字、无前导零、范围 1-65535。 func parsePort(value string) (int, error) { if value == "" || strings.HasPrefix(value, "0") || !isDigits(value) { return 0, fmt.Errorf("invalid sock port %q", value) @@ -257,7 +251,7 @@ func isDigits(s string) bool { return true } -// syncDir 尽力持久化目录项,使 rename 结果落盘;失败不影响写入结果。 +// syncDir 持久化目录项,使 rename 结果落盘;失败不影响写入结果。 func syncDir(dir string) { d, err := os.Open(dir) if err != nil { diff --git a/core/internal/pkg/portinfo/portinfo_test.go b/core/internal/pkg/portinfo/portinfo_test.go index 04d388647..7688b77f2 100644 --- a/core/internal/pkg/portinfo/portinfo_test.go +++ b/core/internal/pkg/portinfo/portinfo_test.go @@ -1,8 +1,6 @@ package portinfo import ( - "bytes" - "encoding/base64" "os" "path/filepath" "runtime" @@ -10,17 +8,12 @@ import ( "testing" ) -// testToken 返回确定性的合法 256-bit token,避免测试依赖随机数。 -func testToken() string { - return base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{0x42}, authTokenBytes)) -} - func validUnixContent() string { - return "protocol=1\nunix=/tmp/swanlab/core.sock\nauth_token=" + testToken() + "\nEOF\n" + return "protocol=1\npid=4242\nunix=/tmp/swanlab/core.sock\nEOF\n" } func TestMarshalUnixFormat(t *testing.T) { - data, err := Marshal(&Info{Protocol: 1, UnixPath: "/tmp/swanlab/core.sock", AuthToken: testToken()}) + data, err := Marshal(&Info{Protocol: 1, PID: 4242, UnixPath: "/tmp/swanlab/core.sock"}) if err != nil { t.Fatalf("Marshal: %v", err) } @@ -30,11 +23,11 @@ func TestMarshalUnixFormat(t *testing.T) { } func TestMarshalSockFormat(t *testing.T) { - data, err := Marshal(&Info{Protocol: 1, SockPort: 12345, AuthToken: testToken()}) + data, err := Marshal(&Info{Protocol: 1, PID: 4242, SockPort: 12345}) if err != nil { t.Fatalf("Marshal: %v", err) } - want := "protocol=1\nsock=12345\nauth_token=" + testToken() + "\nEOF\n" + want := "protocol=1\npid=4242\nsock=12345\nEOF\n" if got := string(data); got != want { t.Fatalf("Marshal output mismatch:\n got: %q\nwant: %q", got, want) } @@ -43,12 +36,8 @@ func TestMarshalSockFormat(t *testing.T) { func TestWriteAndParseRoundTrip(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "core.port") - token, err := NewAuthToken() - if err != nil { - t.Fatalf("NewAuthToken: %v", err) - } - want := Info{Protocol: 1, UnixPath: "/tmp/swanlab/core.sock", AuthToken: token} - err = WriteFile(path, &want) + want := Info{Protocol: 1, PID: 4242, UnixPath: "/tmp/swanlab/core.sock"} + err := WriteFile(path, &want) if err != nil { t.Fatalf("WriteFile: %v", err) } @@ -83,17 +72,12 @@ func TestWriteAndParseRoundTrip(t *testing.T) { func TestWriteFileAtomicallyReplaces(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "core.port") - first := Info{Protocol: 1, UnixPath: "/tmp/a.sock", AuthToken: testToken()} + first := Info{Protocol: 1, PID: 4242, UnixPath: "/tmp/a.sock"} if err := WriteFile(path, &first); err != nil { t.Fatalf("first WriteFile: %v", err) } - secondToken, err := NewAuthToken() - if err != nil { - t.Fatalf("NewAuthToken: %v", err) - } - second := Info{Protocol: 1, UnixPath: "/tmp/b.sock", AuthToken: secondToken} - err = WriteFile(path, &second) - if err != nil { + second := Info{Protocol: 1, PID: 4343, UnixPath: "/tmp/b.sock"} + if err := WriteFile(path, &second); err != nil { t.Fatalf("second WriteFile: %v", err) } got, err := ParseFile(path) @@ -116,12 +100,14 @@ func TestWriteFileRejectsInvalidInfo(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "core.port") cases := map[string]Info{ - "bad protocol": {Protocol: 2, UnixPath: "/tmp/a.sock", AuthToken: testToken()}, - "both endings": {Protocol: 1, UnixPath: "/tmp/a.sock", SockPort: 80, AuthToken: testToken()}, - "no endpoint": {Protocol: 1, AuthToken: testToken()}, - "relative path": {Protocol: 1, UnixPath: "tmp/a.sock", AuthToken: testToken()}, - "port range": {Protocol: 1, SockPort: 65536, AuthToken: testToken()}, - "bad token": {Protocol: 1, UnixPath: "/tmp/a.sock", AuthToken: "short"}, + "bad protocol": {Protocol: 2, PID: 4242, UnixPath: "/tmp/a.sock"}, + "both endings": {Protocol: 1, PID: 4242, UnixPath: "/tmp/a.sock", SockPort: 80}, + "no endpoint": {Protocol: 1, PID: 4242}, + "relative path": {Protocol: 1, PID: 4242, UnixPath: "tmp/a.sock"}, + "port range": {Protocol: 1, PID: 4242, SockPort: 65536}, + "pid zero": {Protocol: 1, PID: 0, UnixPath: "/tmp/a.sock"}, + "pid negative": {Protocol: 1, PID: -1, UnixPath: "/tmp/a.sock"}, + "pid over max": {Protocol: 1, PID: maxPID + 1, UnixPath: "/tmp/a.sock"}, } for name, info := range cases { if err := WriteFile(path, &info); err == nil { @@ -140,36 +126,19 @@ func TestWriteFileRejectsInvalidInfo(t *testing.T) { } } -func TestNewAuthToken(t *testing.T) { - first, err := NewAuthToken() - if err != nil { - t.Fatalf("NewAuthToken: %v", err) - } - second, err := NewAuthToken() - if err != nil { - t.Fatalf("NewAuthToken: %v", err) - } - if first == second { - t.Fatal("two generated tokens must differ") - } - if raw, err := base64.RawURLEncoding.Strict().DecodeString(first); err != nil || len(raw) != authTokenBytes { - t.Fatalf("generated token is not 256-bit base64url: decode err = %v", err) - } -} - func TestParseAcceptsValidVariants(t *testing.T) { - // EOF 行末尾无换行同样合法 + // EOF 行末尾可无换行 content := strings.TrimSuffix(validUnixContent(), "\n") if _, err := Parse([]byte(content)); err != nil { t.Fatalf("parse without trailing newline: %v", err) } // 键值行顺序不影响解析 - reordered := "auth_token=" + testToken() + "\nunix=/tmp/swanlab/core.sock\nprotocol=1\nEOF\n" + reordered := "unix=/tmp/swanlab/core.sock\npid=4242\nprotocol=1\nEOF\n" if _, err := Parse([]byte(reordered)); err != nil { t.Fatalf("parse reordered lines: %v", err) } - // sock 端点(Windows 形态) - sockContent := "protocol=1\nsock=12345\nauth_token=" + testToken() + "\nEOF\n" + // sock 端点(Windows 或 UDS 回退形态) + sockContent := "protocol=1\npid=4242\nsock=12345\nEOF\n" info, err := Parse([]byte(sockContent)) if err != nil { t.Fatalf("parse sock content: %v", err) @@ -181,31 +150,34 @@ func TestParseAcceptsValidVariants(t *testing.T) { func TestParseRejectsMalformed(t *testing.T) { cases := map[string]string{ - "missing EOF": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\n", - "EOF not own line": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "EOF\n", + "missing EOF": "protocol=1\npid=4242\nunix=/tmp/a.sock\n", + "EOF not own line": "protocol=1\npid=4242\nunix=/tmp/a.sockEOF\n", "content after EOF": validUnixContent() + "extra\n", - "torn write": "protocol=1\nunix=/tmp/a.sock\n", + "torn write": "protocol=1\npid=4242\n", "empty body": "EOF\n", - "empty line": "protocol=1\n\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "not key value": "protocol=1\nunix=/tmp/a.sock\nauth_token\nEOF\n", - "unknown key": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nextra=1\nEOF\n", - "duplicate key": "protocol=1\nprotocol=1\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "unknown protocol": "protocol=2\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "non numeric protocol": "protocol=abc\nunix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "missing protocol": "unix=/tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "missing auth_token": "protocol=1\nunix=/tmp/a.sock\nEOF\n", - "legacy auth key": "protocol=1\nunix=/tmp/a.sock\nauth=" + testToken() + "\nEOF\n", - "missing endpoint": "protocol=1\nauth_token=" + testToken() + "\nEOF\n", - "both endpoints": "protocol=1\nunix=/tmp/a.sock\nsock=12345\nauth_token=" + testToken() + "\nEOF\n", - "relative unix path": "protocol=1\nunix=tmp/a.sock\nauth_token=" + testToken() + "\nEOF\n", - "oversize unix path": "protocol=1\nunix=/" + strings.Repeat("a", maxUnixPathLen) + "\nauth_token=" + testToken() + "\nEOF\n", - "port zero": "protocol=1\nsock=0\nauth_token=" + testToken() + "\nEOF\n", - "port range": "protocol=1\nsock=65536\nauth_token=" + testToken() + "\nEOF\n", - "port leading zero": "protocol=1\nsock=01234\nauth_token=" + testToken() + "\nEOF\n", - "port not digits": "protocol=1\nsock=12a45\nauth_token=" + testToken() + "\nEOF\n", - "token too short": "protocol=1\nunix=/tmp/a.sock\nauth_token=QiQi\nEOF\n", - "token std alphabet": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + strings.Repeat("+", 43) + "\nEOF\n", - "token padded": "protocol=1\nunix=/tmp/a.sock\nauth_token=" + strings.TrimSuffix(testToken(), "i") + "i=\nEOF\n", + "empty line": "protocol=1\n\npid=4242\nunix=/tmp/a.sock\nEOF\n", + "not key value": "protocol=1\npid\nunix=/tmp/a.sock\nEOF\n", + "unknown key": "protocol=1\npid=4242\nunix=/tmp/a.sock\nextra=1\nEOF\n", + "legacy mode key": "protocol=1\npid=4242\nmode=owner\nunix=/tmp/a.sock\nEOF\n", + "legacy auth key": "protocol=1\npid=4242\nunix=/tmp/a.sock\nauth_token=abc\nEOF\n", + "duplicate key": "protocol=1\nprotocol=1\npid=4242\nunix=/tmp/a.sock\nEOF\n", + "unknown protocol": "protocol=2\npid=4242\nunix=/tmp/a.sock\nEOF\n", + "non numeric protocol": "protocol=abc\npid=4242\nunix=/tmp/a.sock\nEOF\n", + "missing protocol": "pid=4242\nunix=/tmp/a.sock\nEOF\n", + "missing pid": "protocol=1\nunix=/tmp/a.sock\nEOF\n", + "missing endpoint": "protocol=1\npid=4242\nEOF\n", + "both endpoints": "protocol=1\npid=4242\nunix=/tmp/a.sock\nsock=12345\nEOF\n", + "relative unix path": "protocol=1\npid=4242\nunix=tmp/a.sock\nEOF\n", + "oversize unix path": "protocol=1\npid=4242\nunix=/" + strings.Repeat("a", maxUnixPathLen) + "\nEOF\n", + "port zero": "protocol=1\npid=4242\nsock=0\nEOF\n", + "port range": "protocol=1\npid=4242\nsock=65536\nEOF\n", + "port leading zero": "protocol=1\npid=4242\nsock=01234\nEOF\n", + "port not digits": "protocol=1\npid=4242\nsock=12a45\nEOF\n", + "pid zero": "protocol=1\npid=0\nunix=/tmp/a.sock\nEOF\n", + "pid negative": "protocol=1\npid=-1\nunix=/tmp/a.sock\nEOF\n", + "pid leading zero": "protocol=1\npid=04242\nunix=/tmp/a.sock\nEOF\n", + "pid not digits": "protocol=1\npid=42a42\nunix=/tmp/a.sock\nEOF\n", + "pid out of range": "protocol=1\npid=2147483648\nunix=/tmp/a.sock\nEOF\n", } for name, content := range cases { if _, err := Parse([]byte(content)); err == nil { @@ -215,7 +187,7 @@ func TestParseRejectsMalformed(t *testing.T) { } func TestParseRejectsOversizeContent(t *testing.T) { - content := "protocol=1\nunix=/tmp/" + strings.Repeat("a", MaxFileSize) + "\nauth_token=" + testToken() + "\nEOF\n" + content := "protocol=1\npid=4242\nunix=/tmp/" + strings.Repeat("a", MaxFileSize) + "\nEOF\n" if _, err := Parse([]byte(content)); err == nil { t.Fatal("Parse unexpectedly succeeded for oversize content") } diff --git a/core/internal/server/auth.go b/core/internal/server/auth.go deleted file mode 100644 index 4642565a6..000000000 --- a/core/internal/server/auth.go +++ /dev/null @@ -1,46 +0,0 @@ -package server - -import ( - "context" - "crypto/hmac" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -// AuthTokenMetadataKey 是每个 RPC 必须携带的 auth token metadata 键, -// token 值来自 port-file 的 auth_token 字段。 -const AuthTokenMetadataKey = "auth_token" - -// UnaryAuthInterceptor 校验每个 unary RPC 的 auth token,使用常量时间比较, -// 拒绝缺失或错误的 token。expectedToken 为空表示未启用鉴权,仅限 --listen -// 手动调试场景;SDK 启动约定(--port-filename)总会生成并配置 token。 -func UnaryAuthInterceptor(expectedToken string) grpc.UnaryServerInterceptor { - return func( - ctx context.Context, - req any, - _ *grpc.UnaryServerInfo, - handler grpc.UnaryHandler, - ) (any, error) { - if expectedToken != "" && !requestAuthorized(ctx, expectedToken) { - return nil, status.Error(codes.PermissionDenied, "invalid auth token") - } - return handler(ctx, req) - } -} - -// requestAuthorized 从 incoming metadata 中提取 auth token 并做常量时间比较。 -// token 值不得出现在日志或错误消息中。 -func requestAuthorized(ctx context.Context, expectedToken string) bool { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return false - } - values := md.Get(AuthTokenMetadataKey) - if len(values) != 1 { - return false - } - return hmac.Equal([]byte(values[0]), []byte(expectedToken)) -} diff --git a/core/internal/server/lifecycle_test.go b/core/internal/server/lifecycle_test.go index d95d53386..e465afdf4 100644 --- a/core/internal/server/lifecycle_test.go +++ b/core/internal/server/lifecycle_test.go @@ -14,10 +14,10 @@ import ( runv1 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" ) -// spinup 是测试辅助:用正确的 owner/auth token 把服务置为 READY。 +// spinup 是测试辅助:把服务置为 READY。 func spinup(t *testing.T, env *testEnv) { t.Helper() - if _, err := env.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); err != nil { + if _, err := env.client.SpinupService(callCtx(t), &corev1.SpinupServiceRequest{}); err != nil { t.Fatalf("SpinupService: %v", err) } } @@ -26,7 +26,7 @@ func spinup(t *testing.T, env *testEnv) { func startRun(t *testing.T, env *testEnv) string { t.Helper() resp, err := env.client.DeliverRunStart( - authCtx(t, testAuthToken), + callCtx(t), &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}, ) if err != nil { @@ -70,50 +70,19 @@ func TestLifecycleStateTransitions(t *testing.T) { lc.Close() // 幂等 } -func TestAuthInterceptor(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) - ctx := plainCtx(t) - - // 无 auth metadata:被 interceptor 拒绝 - if _, err := env.client.SpinupService(ctx, &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); status.Code(err) != codes.PermissionDenied { - t.Fatalf("no-auth SpinupService err = %v, want PermissionDenied", err) - } - // 错误 auth token - wrong := authCtx(t, "wrong-auth") - if _, err := env.client.SpinupService(wrong, &corev1.SpinupServiceRequest{OwnerToken: testOwnerToken}); status.Code(err) != codes.PermissionDenied { - t.Fatalf("wrong-auth SpinupService err = %v, want PermissionDenied", err) - } - select { - case <-env.ctrl.Done(): - t.Fatal("auth failure must not trigger shutdown") - case <-afterWindow(): - } -} - -func TestSpinupOwnerCheck(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) - // 正确 auth 但错误 owner token - _, err := env.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: "wrong-owner"}) - if status.Code(err) != codes.PermissionDenied { - t.Fatalf("wrong-owner SpinupService err = %v, want PermissionDenied", err) - } - // 未配置 owner token 的服务一律拒绝 - env2 := newTestEnv(t, "", testAuthToken) - _, err = env2.client.SpinupService(authCtx(t, testAuthToken), &corev1.SpinupServiceRequest{OwnerToken: "any"}) - if status.Code(err) != codes.PermissionDenied { - t.Fatalf("empty-configured-owner SpinupService err = %v, want PermissionDenied", err) - } -} - func TestSpinupIdempotent(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) spinup(t, env) // 重复 Spinup 在 READY 下幂等成功 + // owner_token 字段保留但忽略:任意值不影响 READY 幂等 + if _, err := env.client.SpinupService(callCtx(t), &corev1.SpinupServiceRequest{OwnerToken: "ignored"}); err != nil { + t.Fatalf("SpinupService with ignored owner_token: %v", err) + } } func TestRunRPCsRejectedBeforeReady(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) - ctx := authCtx(t, testAuthToken) + env := newTestEnv(t) + ctx := callCtx(t) checks := []struct { name string call func() error @@ -162,10 +131,10 @@ func TestRunRPCsRejectedBeforeReady(t *testing.T) { } func TestUpsertsUnimplementedAfterReady(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) handle := startRun(t, env) - ctx := authCtx(t, testAuthToken) + ctx := callCtx(t) for _, tc := range []struct { name string call func() error @@ -213,18 +182,18 @@ func TestUpsertsUnimplementedAfterReady(t *testing.T) { } func TestDeliverRunStartRequiresStartRecord(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) - _, err := env.client.DeliverRunStart(authCtx(t, testAuthToken), &corev1.DeliverRunStartRequest{}) + _, err := env.client.DeliverRunStart(callCtx(t), &corev1.DeliverRunStartRequest{}) if status.Code(err) != codes.InvalidArgument { t.Fatalf("DeliverRunStart without start_record: err = %v, want InvalidArgument", err) } } func TestHandleValidation(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) - ctx := authCtx(t, testAuthToken) + ctx := callCtx(t) // 空 handle:InvalidArgument if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{}); status.Code(err) != codes.InvalidArgument { @@ -237,10 +206,10 @@ func TestHandleValidation(t *testing.T) { } func TestRunLifecycleCoreStateMapping(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) handle := startRun(t, env) - ctx := authCtx(t, testAuthToken) + ctx := callCtx(t) // start 后:RUNNING resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) @@ -263,7 +232,7 @@ func TestRunLifecycleCoreStateMapping(t *testing.T) { t.Fatalf("stats state after finish = %v, want FINISHED", resp.GetStats().GetState()) } - // confirm:释放会话,但绝不关闭 server + // confirm:释放会话,不关闭 server if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { t.Fatalf("ConfirmRunFinish: %v", err) } @@ -282,15 +251,15 @@ func TestRunLifecycleCoreStateMapping(t *testing.T) { } func TestTwoRunHandleIsolation(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) handleA, handleB := startRun(t, env), startRun(t, env) if handleA == handleB { t.Fatal("two runs must receive distinct run_handles") } - ctx := authCtx(t, testAuthToken) + ctx := callCtx(t) - // 只结束 A:A 报 FINISHED,B 仍是 RUNNING,互不串扰 + // 结束 A:A 报 FINISHED,B 保持 RUNNING,互不串扰 if _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handleA}); err != nil { t.Fatalf("finish A: %v", err) } @@ -310,7 +279,7 @@ func TestTwoRunHandleIsolation(t *testing.T) { } } - // confirm A:B 仍可正常走完生命周期 + // confirm A:B 可正常走完生命周期 if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handleA}); err != nil { t.Fatalf("confirm A: %v", err) } @@ -326,7 +295,7 @@ func TestTwoRunHandleIsolation(t *testing.T) { // TestConcurrentRunSessions 模拟两个并发 client 各自跑完生命周期, // 配合 -race 验证 session registry 的并发安全与会话隔离。 func TestConcurrentRunSessions(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) + env := newTestEnv(t) spinup(t, env) const clients = 4 @@ -336,7 +305,7 @@ func TestConcurrentRunSessions(t *testing.T) { wg.Add(1) go func(id int) { defer wg.Done() - ctx := authCtx(t, testAuthToken) + ctx := callCtx(t) resp, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) if err != nil { errs <- fmt.Errorf("client %d start: %w", id, err) diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 0ff74714e..4dc91d4e8 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -1,17 +1,17 @@ // Package server 提供 swanlab-core 的 gRPC 服务端实现。 // -// 生命周期(P0 骨架): -// - auth interceptor 校验每个 RPC 的 auth token(port-file 回报); -// - SpinupService 是 owner-only、幂等的 READY 屏障; -// - 五类 Upsert* 在 store/transport 实现前统一返回 UNIMPLEMENTED,不假成功; +// 生命周期(骨架): +// - Owner Mode 不使用应用层 token,信任边界由本地 transport 与文件系统 +// 权限承担(UDS socket 权限、owner-only 私有 runtime 目录、0600 port-file); +// - SpinupService 是幂等的 READY 屏障;proto 中的 owner_token 字段保留但忽略; +// - 五类 Upsert* 在 store/transport 实现前返回 UNIMPLEMENTED,不假成功; // - DeliverRunStart 创建 run_handle 会话,finish/stats/confirm 按 handle 路由; -// - ConfirmRunFinish 只释放对应会话,绝不关闭 gRPC Server; -// - TeardownService 校验 owner token,触发 controller 的统一关闭路径。 +// - ConfirmRunFinish 释放对应会话,不关闭 gRPC Server; +// - TeardownService 触发 controller 的关闭路径。 package server import ( "context" - "crypto/hmac" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -24,25 +24,19 @@ import ( // Service 实现 CoreService。 // -// 除 Teardown/Spinup 与 run 生命周期 RPC 外,五类业务 upsert 由嵌入的 -// Unimplemented 接管并显式改写为 UNIMPLEMENTED(在 READY 门控之后)。 +// 除 Spinup/Teardown 与 run 生命周期 RPC 外,五类业务 upsert 由嵌入的 +// Unimplemented 改写为 UNIMPLEMENTED(READY 门控之后)。 type Service struct { corev1.UnimplementedCoreServiceServer - ownerToken string - authToken string controller *Controller lc *Lifecycle registry *sessionRegistry } -// NewService 创建服务实例。ownerToken 为 spawn owner 通过私有文件传入的 -// 服务级令牌,是唯一允许触发 Spinup/Teardown 的凭证;authToken 为 -// port-file 回报给所有 client 的 RPC 鉴权令牌。 -func NewService(ownerToken, authToken string, controller *Controller) *Service { +// NewService 创建服务实例。 +func NewService(controller *Controller) *Service { return &Service{ - ownerToken: ownerToken, - authToken: authToken, controller: controller, lc: controller.Lifecycle(), registry: newSessionRegistry(), @@ -54,14 +48,6 @@ func (s *Service) Register(g *grpc.Server) { corev1.RegisterCoreServiceServer(g, s) } -// verifyOwner 常量时间校验 owner token;token 值不得出现在日志或错误消息中。 -func (s *Service) verifyOwner(token string) error { - if s.ownerToken == "" || !hmac.Equal([]byte(token), []byte(s.ownerToken)) { - return status.Error(codes.PermissionDenied, "invalid owner token") - } - return nil -} - // requireReady 拒绝 READY 之前的 run 级 RPC。 func (s *Service) requireReady() error { if s.lc.Ready() { @@ -70,7 +56,7 @@ func (s *Service) requireReady() error { return status.Error(codes.FailedPrecondition, "service is not ready; call SpinupService first") } -// rejectUpsert 是五类 upsert 的统一应答:READY 门控通过后必须返回 +// rejectUpsert 是五类 upsert 的应答:READY 门控通过后返回 // UNIMPLEMENTED——store/transport 未实现前不得用 Empty 成功假接收。 func (s *Service) rejectUpsert() error { if err := s.requireReady(); err != nil { @@ -79,25 +65,18 @@ func (s *Service) rejectUpsert() error { return status.Error(codes.Unimplemented, "upsert data path is not implemented in this core build") } -// SpinupService 完成 owner 校验并把服务置为 READY,幂等; -// 已进入 STOPPING/CLOSED 时拒绝。 -func (s *Service) SpinupService(_ context.Context, req *corev1.SpinupServiceRequest) (*corev1.SpinupServiceResponse, error) { - if err := s.verifyOwner(req.GetOwnerToken()); err != nil { - return nil, err - } +// SpinupService 把服务置为 READY,幂等;STOPPING/CLOSED 下拒绝。 +// 请求的 owner_token 字段保留但忽略。 +func (s *Service) SpinupService(_ context.Context, _ *corev1.SpinupServiceRequest) (*corev1.SpinupServiceResponse, error) { if !s.lc.Spinup() { return nil, status.Error(codes.FailedPrecondition, "service is stopping or closed") } return &corev1.SpinupServiceResponse{}, nil } -// TeardownService 关闭整个服务进程,仅接受正确的 owner token。 -// 校验使用常量时间比较;token 值不得出现在日志或错误消息中。 -func (s *Service) TeardownService(_ context.Context, req *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { - if err := s.verifyOwner(req.GetOwnerToken()); err != nil { - return nil, err - } - // 异步触发统一关闭路径,保证本响应先于连接关闭送达调用方。 +// TeardownService 关闭整个服务进程,不校验 owner token;异步触发关闭 +// 路径,保证本响应先于连接关闭送达调用方。 +func (s *Service) TeardownService(_ context.Context, _ *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { s.controller.Shutdown("teardown") return &corev1.TeardownServiceResponse{}, nil } @@ -114,7 +93,7 @@ func (s *Service) DeliverRunStart(_ context.Context, req *corev1.DeliverRunStart if err != nil { return nil, err } - // P0 骨架:仅回显最小有效结果,不落盘、不上传、不访问云端。 + // 骨架:回显最小有效结果,不落盘、不上传、不访问云端。 return &corev1.DeliverRunStartResponse{ Success: true, Run: req.GetStartRecord(), @@ -143,7 +122,7 @@ func (s *Service) UpsertSaves(_ context.Context, _ *corev1.UpsertSavesRequest) ( return nil, s.rejectUpsert() } -// DeliverRunFinish 只结束对应会话的 run,服务保持存活。 +// DeliverRunFinish 结束对应会话的 run,服务保持存活。 func (s *Service) DeliverRunFinish(_ context.Context, req *corev1.DeliverRunFinishRequest) (*corev1.DeliverRunFinishResponse, error) { if err := s.requireReady(); err != nil { return nil, err @@ -171,7 +150,7 @@ func (s *Service) GetOperationStats(_ context.Context, req *corev1.GetOperationS }, nil } -// ConfirmRunFinish 确认对应会话已排空并释放资源,绝不关闭 gRPC Server。 +// ConfirmRunFinish 确认对应会话已排空并释放资源,不关闭 gRPC Server。 func (s *Service) ConfirmRunFinish(_ context.Context, req *corev1.ConfirmRunFinishRequest) (*corev1.ConfirmRunFinishResponse, error) { if err := s.requireReady(); err != nil { return nil, err diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index 34213a31e..467810502 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -7,10 +7,7 @@ import ( "time" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" @@ -21,9 +18,6 @@ const ( testGrace = 2 * time.Second noShutdownWindow = 200 * time.Millisecond callTimeout = 2 * time.Second - - testOwnerToken = "owner-secret" - testAuthToken = "auth-secret" ) type testEnv struct { @@ -31,12 +25,12 @@ type testEnv struct { ctrl *Controller } -// newTestEnv 在内存连接上启动完整服务端(含 auth interceptor),返回客户端句柄与关闭控制器。 -func newTestEnv(t *testing.T, ownerToken, authToken string) *testEnv { +// newTestEnv 在内存连接上启动完整服务端,返回客户端句柄与关闭控制器。 +func newTestEnv(t *testing.T) *testEnv { t.Helper() - g := grpc.NewServer(grpc.ChainUnaryInterceptor(UnaryAuthInterceptor(authToken))) + g := grpc.NewServer() ctrl := NewController(g, testGrace) - NewService(ownerToken, authToken, ctrl).Register(g) + NewService(ctrl).Register(g) lis := bufconn.Listen(bufconnSize) go func() { _ = g.Serve(lis) }() conn, err := grpc.NewClient("passthrough:///bufnet", @@ -56,52 +50,36 @@ func newTestEnv(t *testing.T, ownerToken, authToken string) *testEnv { return &testEnv{client: corev1.NewCoreServiceClient(conn), ctrl: ctrl} } -// authCtx 返回携带 auth token metadata 的带超时 context。 -func authCtx(t *testing.T, token string) context.Context { - t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - t.Cleanup(cancel) - return metadata.AppendToOutgoingContext(ctx, AuthTokenMetadataKey, token) -} - -// plainCtx 返回不带 auth metadata 的带超时 context。 -func plainCtx(t *testing.T) context.Context { +// callCtx 返回带超时的 context。 +func callCtx(t *testing.T) context.Context { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), callTimeout) t.Cleanup(cancel) return ctx } -func TestTeardownServiceRejectsWrongToken(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) - _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: "wrong-token"}) - if status.Code(err) != codes.PermissionDenied { - t.Fatalf("TeardownService err = %v, want PermissionDenied", err) +func TestTeardownServiceShutsDownServer(t *testing.T) { + env := newTestEnv(t) + if _, err := env.client.TeardownService(callCtx(t), &corev1.TeardownServiceRequest{}); err != nil { + t.Fatalf("TeardownService: %v", err) } select { case <-env.ctrl.Done(): - t.Fatal("wrong owner token must not trigger shutdown") - case <-time.After(noShutdownWindow): + case <-time.After(callTimeout): + t.Fatal("shutdown not completed after valid teardown") } } -func TestTeardownServiceShutsDownServer(t *testing.T) { - env := newTestEnv(t, testOwnerToken, testAuthToken) - if _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: testOwnerToken}); err != nil { +// TestTeardownServiceIgnoresOwnerToken 不校验 owner_token:proto 字段保留但忽略。 +func TestTeardownServiceIgnoresOwnerToken(t *testing.T) { + env := newTestEnv(t) + if _, err := env.client.TeardownService(callCtx(t), &corev1.TeardownServiceRequest{OwnerToken: "ignored"}); err != nil { t.Fatalf("TeardownService: %v", err) } select { case <-env.ctrl.Done(): case <-time.After(callTimeout): - t.Fatal("shutdown not completed after valid teardown") - } -} - -func TestTeardownServiceRejectsEmptyConfiguredToken(t *testing.T) { - env := newTestEnv(t, "", testAuthToken) - _, err := env.client.TeardownService(authCtx(t, testAuthToken), &corev1.TeardownServiceRequest{OwnerToken: ""}) - if status.Code(err) != codes.PermissionDenied { - t.Fatalf("TeardownService err = %v, want PermissionDenied", err) + t.Fatal("shutdown not completed after teardown") } } From 76f8f3e746878b5573e8737a78b68d12cb59e9a8 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Thu, 24 Sep 2026 11:17:14 +0800 Subject: [PATCH 13/15] refactor: correct zero-token definition --- core/cmd/swanlab-core/main.go | 29 ++++---- core/internal/pkg/process/parent_linux.go | 2 + core/internal/server/lifecycle_test.go | 74 ++++++++++++++++++- core/internal/server/server.go | 22 +++--- core/internal/server/server_test.go | 13 ---- core/internal/server/session.go | 26 ++++++- core/proto/swanlab/grpc/core/v1/core.pb.go | 32 ++------ .../swanlab/grpc/core/v1/core_grpc.pb.go | 18 +++-- protos/swanlab/grpc/core/v1/core.proto | 15 ++-- .../proto/swanlab/grpc/core/v1/core_pb2.py | 72 +++++++++--------- .../proto/swanlab/grpc/core/v1/core_pb2.pyi | 12 +-- .../swanlab/grpc/core/v1/core_pb2_grpc.py | 15 ++-- 12 files changed, 193 insertions(+), 137 deletions(-) diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index de64b472a..cf43b1634 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -14,13 +14,12 @@ // core.sock(UDS,目录需已存在),listen 失败记录 warning 后回退 // 127.0.0.1 随机回环端口;Windows 使用随机回环端口。 // -// 信任模型:Owner Mode 不使用应用层 token,安全边界由本地 transport 与 -// 文件系统权限承担:POSIX 使用 UDS(socket 文件位于 owner-only 私有 -// runtime 目录),port-file 以 0600 权限原子发布;TCP 回退对同机同用户 -// 进程开放,不提供应用层访问控制。 +// 信任模型:不使用应用层 token。POSIX 使用 UDS(socket 位于 owner-only +// 私有 runtime 目录),port-file 以 0600 原子发布;loopback TCP 回退 +// 不隔离本机用户,能连接端口的本机进程可调用全部 RPC(含 Teardown)。 // // --detach 与 --idle-timeout 为 detached 模式预留:detached 未实现, -// 传入报用法错误退出。 +// 传入时忽略(no-op)并记录 warning,core 仍以 owner 模式运行。 // // 生命周期:Teardown RPC、SIGINT/SIGTERM、父进程退出(process 包监控)或 // Serve 异常汇入 service controller 的关闭路径(GracefulStop → 超时强制 @@ -90,9 +89,9 @@ func run(args []string) int { parentPID := fs.Int("parent-pid", envInt(envParentPID), "expected parent PID; core exits when the parent exits, defaults to the actual parent at startup") detach := fs.Bool("detach", false, - "detached mode (not implemented in this build)") + "detached mode (reserved; accepted but ignored in this build)") idleTimeout := fs.Duration("idle-timeout", 0, - "detached idle timeout (not implemented in this build)") + "detached idle timeout (reserved; accepted but ignored in this build)") if err := fs.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { return 0 @@ -105,8 +104,7 @@ func run(args []string) int { return 0 } if *detach || *idleTimeout != 0 { - console.Error("--detach/--idle-timeout: detached mode is not implemented in this build") - return exitUsageError + console.Warning("--detach/--idle-timeout accepted but ignored: detached mode is not implemented; running in owner mode") } if *listenAddr == "" && *portFilename == "" { console.Error("no listen endpoint: pass --listen (manual debug) or --port-filename (SDK startup convention)") @@ -182,6 +180,7 @@ func run(args []string) int { exitCode := 0 var cause string var serveFailure error + served := false select { case <-ctx.Done(): console.Info("shutdown signal received, stopping") @@ -189,15 +188,15 @@ func run(args []string) int { case <-parentExited: console.Warning("parent process exited, stopping core") cause = "parent-exit" - case err := <-serveErr: - serveFailure = err + case serveFailure = <-serveErr: + served = true cause = "serve-error" } ctrl.Shutdown(cause) - // 等待 Serve 返回与关闭序列完成(两者任一先行均可)。 - select { - case serveFailure = <-serveErr: - case <-ctrl.Done(): + // Shutdown 的关闭序列会让 Serve 返回(GracefulStop 超时转 Stop); + // 上面的 select 未消费 serveErr 时,在此收取其结果。 + if !served { + serveFailure = <-serveErr } <-ctrl.Done() if serveFailure != nil { diff --git a/core/internal/pkg/process/parent_linux.go b/core/internal/pkg/process/parent_linux.go index 22e9feecc..b2c0a80a1 100644 --- a/core/internal/pkg/process/parent_linux.go +++ b/core/internal/pkg/process/parent_linux.go @@ -31,6 +31,8 @@ func notifyOnParentExit(parentPID int) (<-chan struct{}, error) { // 快路径:PR_SET_PDEATHSIG 绑定的是调用 prctl 时的父进程线程。 // 若 Python 从非主线程 spawn 且该线程先于进程退出,PDEATHSIG 只会触发一次 // 伪通知,由下方的 PPID 复查过滤;此后不会再有信号,必须依赖进程级兜底。 + // SIGUSR1 保留给父死监控,signal.Notify 接管后不再触发默认终止行为, + // 其他组件不得复用该信号。 parentDeathSignals := make(chan os.Signal, 1) signal.Notify(parentDeathSignals, unix.SIGUSR1) if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGUSR1), 0, 0, 0); err != nil { diff --git a/core/internal/server/lifecycle_test.go b/core/internal/server/lifecycle_test.go index e465afdf4..3c328e825 100644 --- a/core/internal/server/lifecycle_test.go +++ b/core/internal/server/lifecycle_test.go @@ -74,10 +74,6 @@ func TestSpinupIdempotent(t *testing.T) { env := newTestEnv(t) spinup(t, env) spinup(t, env) // 重复 Spinup 在 READY 下幂等成功 - // owner_token 字段保留但忽略:任意值不影响 READY 幂等 - if _, err := env.client.SpinupService(callCtx(t), &corev1.SpinupServiceRequest{OwnerToken: "ignored"}); err != nil { - t.Fatalf("SpinupService with ignored owner_token: %v", err) - } } func TestRunRPCsRejectedBeforeReady(t *testing.T) { @@ -250,6 +246,76 @@ func TestRunLifecycleCoreStateMapping(t *testing.T) { } } +// TestConfirmBeforeFinishRejected 验证 ConfirmRunFinish 的前置条件: +// finish 未交付时 confirm 返回 FailedPrecondition 且会话保留, +// 之后可完成 finish/confirm 流程。 +func TestConfirmBeforeFinishRejected(t *testing.T) { + env := newTestEnv(t) + spinup(t, env) + handle := startRun(t, env) + ctx := callCtx(t) + + if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("confirm before finish err = %v, want FailedPrecondition", err) + } + // 会话未被摘除:stats 报 RUNNING + resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_RUNNING { + t.Fatalf("stats after rejected confirm: err = %v, state = %v, want RUNNING", err, resp.GetStats().GetState()) + } + // finish 后 confirm 成功,会话被释放 + if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("DeliverRunFinish: %v", err) + } + if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("ConfirmRunFinish after finish: %v", err) + } + if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { + t.Fatalf("stats after confirm err = %v, want NotFound", err) + } +} + +// TestFinishConfirmRace 并发执行 finish 与 confirm,验证 registry 的 +// 原子校验:confirm 在 finish 标记后才会摘除会话,因此 finish 必然成功; +// confirm 的合法结果为成功(后于 finish)或 FailedPrecondition(先于 finish)。 +func TestFinishConfirmRace(t *testing.T) { + env := newTestEnv(t) + spinup(t, env) + ctx := callCtx(t) + + for i := 0; i < 20; i++ { + handle := startRun(t, env) + var wg sync.WaitGroup + var finishErr, confirmErr error + wg.Add(2) + go func() { + defer wg.Done() + _, finishErr = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}) + }() + go func() { + defer wg.Done() + _, confirmErr = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}) + }() + wg.Wait() + + if finishErr != nil { + t.Fatalf("iter %d: finish must always succeed (session must survive a losing confirm): %v", i, finishErr) + } + if confirmErr != nil && status.Code(confirmErr) != codes.FailedPrecondition { + t.Fatalf("iter %d: confirm err = %v, want nil or FailedPrecondition", i, confirmErr) + } + // confirm 输了竞态时会话未释放,补一次 confirm + if confirmErr != nil { + if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("iter %d: retry confirm after finish: %v", i, err) + } + } + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { + t.Fatalf("iter %d: stats after release err = %v, want NotFound", i, err) + } + } +} + func TestTwoRunHandleIsolation(t *testing.T) { env := newTestEnv(t) spinup(t, env) diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 4dc91d4e8..775bd106d 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -1,12 +1,13 @@ // Package server 提供 swanlab-core 的 gRPC 服务端实现。 // // 生命周期(骨架): -// - Owner Mode 不使用应用层 token,信任边界由本地 transport 与文件系统 -// 权限承担(UDS socket 权限、owner-only 私有 runtime 目录、0600 port-file); -// - SpinupService 是幂等的 READY 屏障;proto 中的 owner_token 字段保留但忽略; -// - 五类 Upsert* 在 store/transport 实现前返回 UNIMPLEMENTED,不假成功; +// - 不使用应用层 token;信任边界是本地 transport 与文件系统权限 +// (UDS socket、owner-only runtime 目录、0600 port-file),能连接端点的 +// client 可调用全部 RPC,包括 Teardown; +// - SpinupService 是幂等的 READY 屏障; +// - 五类 Upsert* 在 store/transport 实现前返回 UNIMPLEMENTED; // - DeliverRunStart 创建 run_handle 会话,finish/stats/confirm 按 handle 路由; -// - ConfirmRunFinish 释放对应会话,不关闭 gRPC Server; +// - ConfirmRunFinish 在 finish 交付后原子释放对应会话,不关闭 gRPC Server; // - TeardownService 触发 controller 的关闭路径。 package server @@ -66,7 +67,6 @@ func (s *Service) rejectUpsert() error { } // SpinupService 把服务置为 READY,幂等;STOPPING/CLOSED 下拒绝。 -// 请求的 owner_token 字段保留但忽略。 func (s *Service) SpinupService(_ context.Context, _ *corev1.SpinupServiceRequest) (*corev1.SpinupServiceResponse, error) { if !s.lc.Spinup() { return nil, status.Error(codes.FailedPrecondition, "service is stopping or closed") @@ -74,8 +74,8 @@ func (s *Service) SpinupService(_ context.Context, _ *corev1.SpinupServiceReques return &corev1.SpinupServiceResponse{}, nil } -// TeardownService 关闭整个服务进程,不校验 owner token;异步触发关闭 -// 路径,保证本响应先于连接关闭送达调用方。 +// TeardownService 关闭整个服务进程。无 owner 校验:能连接端点的 client +// 都可调用,SDK 约定由 owner 发起。异步触发关闭路径,响应先于连接关闭送达。 func (s *Service) TeardownService(_ context.Context, _ *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { s.controller.Shutdown("teardown") return &corev1.TeardownServiceResponse{}, nil @@ -150,14 +150,14 @@ func (s *Service) GetOperationStats(_ context.Context, req *corev1.GetOperationS }, nil } -// ConfirmRunFinish 确认对应会话已排空并释放资源,不关闭 gRPC Server。 +// ConfirmRunFinish 确认对应会话已排空并原子释放资源,不关闭 gRPC Server。 +// finish 未交付时返回 FAILED_PRECONDITION,会话保留。 func (s *Service) ConfirmRunFinish(_ context.Context, req *corev1.ConfirmRunFinishRequest) (*corev1.ConfirmRunFinishResponse, error) { if err := s.requireReady(); err != nil { return nil, err } - if _, err := s.registry.lookup(req.GetRunHandle()); err != nil { + if err := s.registry.confirm(req.GetRunHandle()); err != nil { return nil, err } - s.registry.release(req.GetRunHandle()) return &corev1.ConfirmRunFinishResponse{Success: true}, nil } diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index 467810502..623044efb 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -70,19 +70,6 @@ func TestTeardownServiceShutsDownServer(t *testing.T) { } } -// TestTeardownServiceIgnoresOwnerToken 不校验 owner_token:proto 字段保留但忽略。 -func TestTeardownServiceIgnoresOwnerToken(t *testing.T) { - env := newTestEnv(t) - if _, err := env.client.TeardownService(callCtx(t), &corev1.TeardownServiceRequest{OwnerToken: "ignored"}); err != nil { - t.Fatalf("TeardownService: %v", err) - } - select { - case <-env.ctrl.Done(): - case <-time.After(callTimeout): - t.Fatal("shutdown not completed after teardown") - } -} - func TestControllerShutdownIdempotent(t *testing.T) { g := grpc.NewServer() ctrl := NewController(g, testGrace) diff --git a/core/internal/server/session.go b/core/internal/server/session.go index 751909515..0c71cb394 100644 --- a/core/internal/server/session.go +++ b/core/internal/server/session.go @@ -33,6 +33,14 @@ func (s *runSession) finish() { s.state = sessionFinishStaged } +// finished 报告 finish 是否完成 record 上传 +// ConfirmRunFinish 的前置条件 +func (s *runSession) finished() bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.state == sessionFinishStaged +} + // coreState 把内部会话状态映射为线上契约 CoreState(run 级数据排空轴)。 // finish 交付后骨架无数据可排空,直接报 FINISHED,drain 轮询即可判停。 func (s *runSession) coreState() operationv1.CoreState { @@ -88,9 +96,23 @@ func (r *sessionRegistry) create() (string, *runSession, error) { return handle, s, nil } -// release 摘除会话,仅由 ConfirmRunFinish 成功路径调用。 -func (r *sessionRegistry) release(handle string) { +// confirm 在 registry 锁下原子校验并摘除会话:handle 为空返回 +// InvalidArgument,未知或已释放返回 NotFound,finish 未交付返回 +// FailedPrecondition 且会话保留。confirm 成功时 finish 已生效。 +// 锁顺序:registry mu → session mu。 +func (r *sessionRegistry) confirm(handle string) error { + if handle == "" { + return status.Error(codes.InvalidArgument, "run_handle must not be empty") + } r.mu.Lock() defer r.mu.Unlock() + s, ok := r.sessions[handle] + if !ok { + return status.Error(codes.NotFound, "unknown or released run_handle") + } + if !s.finished() { + return status.Error(codes.FailedPrecondition, "run finish has not been delivered for this handle") + } delete(r.sessions, handle) + return nil } diff --git a/core/proto/swanlab/grpc/core/v1/core.pb.go b/core/proto/swanlab/grpc/core/v1/core.pb.go index f58d0fe1e..31457e422 100644 --- a/core/proto/swanlab/grpc/core/v1/core.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core.pb.go @@ -29,10 +29,9 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// SpinupService 请求。 +// SpinupService 请求。无身份字段:core 面向同一本地信任域,不做应用层鉴权。 type SpinupServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -67,13 +66,6 @@ func (*SpinupServiceRequest) Descriptor() ([]byte, []int) { return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{0} } -func (x *SpinupServiceRequest) GetOwnerToken() string { - if x != nil { - return x.OwnerToken - } - return "" -} - // SpinupService 响应。无字段,返回即表示服务已 READY。 type SpinupServiceResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -111,10 +103,9 @@ func (*SpinupServiceResponse) Descriptor() ([]byte, []int) { return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{1} } -// TeardownService 请求。 +// TeardownService 请求。无身份字段,同 SpinupServiceRequest。 type TeardownServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - OwnerToken string `protobuf:"bytes,1,opt,name=owner_token,json=ownerToken,proto3" json:"owner_token,omitempty"` // 服务所有者令牌,仅 spawn owner 持有 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -149,13 +140,6 @@ func (*TeardownServiceRequest) Descriptor() ([]byte, []int) { return file_swanlab_grpc_core_v1_core_proto_rawDescGZIP(), []int{2} } -func (x *TeardownServiceRequest) GetOwnerToken() string { - if x != nil { - return x.OwnerToken - } - return "" -} - // TeardownService 响应。 type TeardownServiceResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -934,14 +918,10 @@ var File_swanlab_grpc_core_v1_core_proto protoreflect.FileDescriptor const file_swanlab_grpc_core_v1_core_proto_rawDesc = "" + "\n" + - "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"7\n" + - "\x14SpinupServiceRequest\x12\x1f\n" + - "\vowner_token\x18\x01 \x01(\tR\n" + - "ownerToken\"\x17\n" + - "\x15SpinupServiceResponse\"9\n" + - "\x16TeardownServiceRequest\x12\x1f\n" + - "\vowner_token\x18\x01 \x01(\tR\n" + - "ownerToken\"\x19\n" + + "\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x16\n" + + "\x14SpinupServiceRequest\"\x17\n" + + "\x15SpinupServiceResponse\"\x18\n" + + "\x16TeardownServiceRequest\"\x19\n" + "\x17TeardownServiceResponse\"\xa5\x01\n" + "\x16DeliverRunStartRequest\x12K\n" + "\rcore_settings\x18\x01 \x01(\v2&.swanlab.settings.core.v1.CoreSettingsR\fcoreSettings\x12>\n" + diff --git a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go index c9e032da1..213732827 100644 --- a/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go +++ b/core/proto/swanlab/grpc/core/v1/core_grpc.pb.go @@ -43,14 +43,14 @@ const ( // // 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; +// 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceClient interface { // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 SpinupService(ctx context.Context, in *SpinupServiceRequest, opts ...grpc.CallOption) (*SpinupServiceResponse, error) - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + // TeardownService 关闭整个服务进程。不同于 ConfirmRunFinish ,能连接 core 的 client 都可调用。 TeardownService(ctx context.Context, in *TeardownServiceRequest, opts ...grpc.CallOption) (*TeardownServiceResponse, error) // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 DeliverRunStart(ctx context.Context, in *DeliverRunStartRequest, opts ...grpc.CallOption) (*DeliverRunStartResponse, error) @@ -68,7 +68,8 @@ type CoreServiceClient interface { DeliverRunFinish(ctx context.Context, in *DeliverRunFinishRequest, opts ...grpc.CallOption) (*DeliverRunFinishResponse, error) // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 GetOperationStats(ctx context.Context, in *GetOperationStatsRequest, opts ...grpc.CallOption) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + // ConfirmRunFinish 确认指定 run 已排空并释放其资源,不关闭 gRPC Server。 + // 前置条件:该 run 的 DeliverRunFinish 已上传完成,提前调用返回 FAILED_PRECONDITION。 ConfirmRunFinish(ctx context.Context, in *ConfirmRunFinishRequest, opts ...grpc.CallOption) (*ConfirmRunFinishResponse, error) } @@ -200,14 +201,14 @@ func (c *coreServiceClient) ConfirmRunFinish(ctx context.Context, in *ConfirmRun // // 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; +// 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 type CoreServiceServer interface { // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 SpinupService(context.Context, *SpinupServiceRequest) (*SpinupServiceResponse, error) - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + // TeardownService 关闭整个服务进程。不同于 ConfirmRunFinish ,能连接 core 的 client 都可调用。 TeardownService(context.Context, *TeardownServiceRequest) (*TeardownServiceResponse, error) // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 DeliverRunStart(context.Context, *DeliverRunStartRequest) (*DeliverRunStartResponse, error) @@ -225,7 +226,8 @@ type CoreServiceServer interface { DeliverRunFinish(context.Context, *DeliverRunFinishRequest) (*DeliverRunFinishResponse, error) // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 GetOperationStats(context.Context, *GetOperationStatsRequest) (*GetOperationStatsResponse, error) - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + // ConfirmRunFinish 确认指定 run 已排空并释放其资源,不关闭 gRPC Server。 + // 前置条件:该 run 的 DeliverRunFinish 已上传完成,提前调用返回 FAILED_PRECONDITION。 ConfirmRunFinish(context.Context, *ConfirmRunFinishRequest) (*ConfirmRunFinishResponse, error) mustEmbedUnimplementedCoreServiceServer() } diff --git a/protos/swanlab/grpc/core/v1/core.proto b/protos/swanlab/grpc/core/v1/core.proto index 93ab7c7f0..6e921937c 100644 --- a/protos/swanlab/grpc/core/v1/core.proto +++ b/protos/swanlab/grpc/core/v1/core.proto @@ -19,15 +19,15 @@ option go_package = "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1 // // 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; // 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; -// 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; +// 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; +// 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; // 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 service CoreService { // SpinupService 完成服务级初始化并把服务置为 READY,不负责创建 run 级 datastore 和 transport // 启动失败 RPC 返回 FAILED_PRECONDITION;重复 Spinup 在 READY 下幂等成功,共享一个 core 服务进程。 rpc SpinupService(SpinupServiceRequest) returns (SpinupServiceResponse); - // TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - // 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + // TeardownService 关闭整个服务进程。不同于 ConfirmRunFinish ,能连接 core 的 client 都可调用。 rpc TeardownService(TeardownServiceRequest) returns (TeardownServiceResponse); // DeliverRunStart 接收单条 StartRecord,用于实验开始,并返回必要的信息。 @@ -54,22 +54,21 @@ service CoreService { // GetOperationStats 返回指定 run 当前运行状态和上传进度快照。 rpc GetOperationStats(GetOperationStatsRequest) returns (GetOperationStatsResponse); - // ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + // ConfirmRunFinish 确认指定 run 已排空并释放其资源,不关闭 gRPC Server。 + // 前置条件:该 run 的 DeliverRunFinish 已上传完成,提前调用返回 FAILED_PRECONDITION。 rpc ConfirmRunFinish(ConfirmRunFinishRequest) returns (ConfirmRunFinishResponse); } -// SpinupService 请求。 +// SpinupService 请求。无身份字段:core 面向同一本地信任域,不做应用层鉴权。 message SpinupServiceRequest { - string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 } // SpinupService 响应。无字段,返回即表示服务已 READY。 message SpinupServiceResponse { } -// TeardownService 请求。 +// TeardownService 请求。无身份字段,同 SpinupServiceRequest。 message TeardownServiceRequest { - string owner_token = 1; // 服务所有者令牌,仅 spawn owner 持有 } // TeardownService 响应。 diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py index 6f4035580..216bacbe0 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.py @@ -32,7 +32,7 @@ from swanlab.proto.swanlab.settings.core.v1 import core_pb2 as swanlab_dot_settings_dot_core_dot_v1_dot_core__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"+\n\x14SpinupServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x17\n\x15SpinupServiceResponse\"-\n\x16TeardownServiceRequest\x12\x13\n\x0bowner_token\x18\x01 \x01(\t\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xce\x08\n\x0b\x43oreService\x12h\n\rSpinupService\x12*.swanlab.grpc.core.v1.SpinupServiceRequest\x1a+.swanlab.grpc.core.v1.SpinupServiceResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fswanlab/grpc/core/v1/core.proto\x12\x14swanlab.grpc.core.v1\x1a\x1bgoogle/protobuf/empty.proto\x1a\x18swanlab/run/v1/run.proto\x1a!swanlab/metric/data/v1/data.proto\x1a%swanlab/metric/column/v1/column.proto\x1a\x1dswanlab/terminal/v1/log.proto\x1a\x1aswanlab/save/v1/save.proto\x1a$swanlab/operation/v1/operation.proto\x1a#swanlab/settings/core/v1/core.proto\"\x16\n\x14SpinupServiceRequest\"\x17\n\x15SpinupServiceResponse\"\x18\n\x16TeardownServiceRequest\"\x19\n\x17TeardownServiceResponse\"\x8a\x01\n\x16\x44\x65liverRunStartRequest\x12=\n\rcore_settings\x18\x01 \x01(\x0b\x32&.swanlab.settings.core.v1.CoreSettings\x12\x31\n\x0cstart_record\x18\x02 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\"\xde\x01\n\x17\x44\x65liverRunStartResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12(\n\x03run\x18\x03 \x01(\x0b\x32\x1b.swanlab.run.v1.StartRecord\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0bglobal_step\x18\x06 \x01(\x03\x12\x1a\n\x12global_system_step\x18\x07 \x01(\x03\x12\x16\n\x0enew_experiment\x18\x08 \x01(\x08\x12\x12\n\nrun_handle\x18\t \x01(\t\"c\n\x14UpsertColumnsRequest\x12\x37\n\x07\x63olumns\x18\x01 \x03(\x0b\x32&.swanlab.metric.column.v1.ColumnRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"^\n\x14UpsertScalarsRequest\x12\x32\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32$.swanlab.metric.data.v1.ScalarRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"[\n\x12UpsertMediaRequest\x12\x31\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32#.swanlab.metric.data.v1.MediaRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"U\n\x11UpsertLogsRequest\x12,\n\x04logs\x18\x01 \x03(\x0b\x32\x1e.swanlab.terminal.v1.LogRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"T\n\x12UpsertSavesRequest\x12*\n\x05saves\x18\x01 \x03(\x0b\x32\x1b.swanlab.save.v1.SaveRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"b\n\x17\x44\x65liverRunFinishRequest\x12\x33\n\rfinish_record\x18\x01 \x01(\x0b\x32\x1c.swanlab.run.v1.FinishRecord\x12\x12\n\nrun_handle\x18\x02 \x01(\t\"<\n\x18\x44\x65liverRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\".\n\x18GetOperationStatsRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"r\n\x19GetOperationStatsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x05stats\x18\x03 \x01(\x0b\x32$.swanlab.operation.v1.OperationStats\"-\n\x17\x43onfirmRunFinishRequest\x12\x12\n\nrun_handle\x18\x01 \x01(\t\"<\n\x18\x43onfirmRunFinishResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t2\xce\x08\n\x0b\x43oreService\x12h\n\rSpinupService\x12*.swanlab.grpc.core.v1.SpinupServiceRequest\x1a+.swanlab.grpc.core.v1.SpinupServiceResponse\x12n\n\x0fTeardownService\x12,.swanlab.grpc.core.v1.TeardownServiceRequest\x1a-.swanlab.grpc.core.v1.TeardownServiceResponse\x12n\n\x0f\x44\x65liverRunStart\x12,.swanlab.grpc.core.v1.DeliverRunStartRequest\x1a-.swanlab.grpc.core.v1.DeliverRunStartResponse\x12S\n\rUpsertColumns\x12*.swanlab.grpc.core.v1.UpsertColumnsRequest\x1a\x16.google.protobuf.Empty\x12S\n\rUpsertScalars\x12*.swanlab.grpc.core.v1.UpsertScalarsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertMedia\x12(.swanlab.grpc.core.v1.UpsertMediaRequest\x1a\x16.google.protobuf.Empty\x12M\n\nUpsertLogs\x12\'.swanlab.grpc.core.v1.UpsertLogsRequest\x1a\x16.google.protobuf.Empty\x12O\n\x0bUpsertSaves\x12(.swanlab.grpc.core.v1.UpsertSavesRequest\x1a\x16.google.protobuf.Empty\x12q\n\x10\x44\x65liverRunFinish\x12-.swanlab.grpc.core.v1.DeliverRunFinishRequest\x1a..swanlab.grpc.core.v1.DeliverRunFinishResponse\x12t\n\x11GetOperationStats\x12..swanlab.grpc.core.v1.GetOperationStatsRequest\x1a/.swanlab.grpc.core.v1.GetOperationStatsResponse\x12q\n\x10\x43onfirmRunFinish\x12-.swanlab.grpc.core.v1.ConfirmRunFinishRequest\x1a..swanlab.grpc.core.v1.ConfirmRunFinishResponseBDZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -41,39 +41,39 @@ _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'ZBgithub.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1;corev1' _globals['_SPINUPSERVICEREQUEST']._serialized_start=320 - _globals['_SPINUPSERVICEREQUEST']._serialized_end=363 - _globals['_SPINUPSERVICERESPONSE']._serialized_start=365 - _globals['_SPINUPSERVICERESPONSE']._serialized_end=388 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=390 - _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=435 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=437 - _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=462 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=465 - _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=603 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=606 - _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=828 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=830 - _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=929 - _globals['_UPSERTSCALARSREQUEST']._serialized_start=931 - _globals['_UPSERTSCALARSREQUEST']._serialized_end=1025 - _globals['_UPSERTMEDIAREQUEST']._serialized_start=1027 - _globals['_UPSERTMEDIAREQUEST']._serialized_end=1118 - _globals['_UPSERTLOGSREQUEST']._serialized_start=1120 - _globals['_UPSERTLOGSREQUEST']._serialized_end=1205 - _globals['_UPSERTSAVESREQUEST']._serialized_start=1207 - _globals['_UPSERTSAVESREQUEST']._serialized_end=1291 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1293 - _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1391 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1393 - _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1453 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1455 - _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1501 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1503 - _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1617 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1619 - _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1664 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1666 - _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1726 - _globals['_CORESERVICE']._serialized_start=1729 - _globals['_CORESERVICE']._serialized_end=2831 + _globals['_SPINUPSERVICEREQUEST']._serialized_end=342 + _globals['_SPINUPSERVICERESPONSE']._serialized_start=344 + _globals['_SPINUPSERVICERESPONSE']._serialized_end=367 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_start=369 + _globals['_TEARDOWNSERVICEREQUEST']._serialized_end=393 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_start=395 + _globals['_TEARDOWNSERVICERESPONSE']._serialized_end=420 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_start=423 + _globals['_DELIVERRUNSTARTREQUEST']._serialized_end=561 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_start=564 + _globals['_DELIVERRUNSTARTRESPONSE']._serialized_end=786 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_start=788 + _globals['_UPSERTCOLUMNSREQUEST']._serialized_end=887 + _globals['_UPSERTSCALARSREQUEST']._serialized_start=889 + _globals['_UPSERTSCALARSREQUEST']._serialized_end=983 + _globals['_UPSERTMEDIAREQUEST']._serialized_start=985 + _globals['_UPSERTMEDIAREQUEST']._serialized_end=1076 + _globals['_UPSERTLOGSREQUEST']._serialized_start=1078 + _globals['_UPSERTLOGSREQUEST']._serialized_end=1163 + _globals['_UPSERTSAVESREQUEST']._serialized_start=1165 + _globals['_UPSERTSAVESREQUEST']._serialized_end=1249 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_start=1251 + _globals['_DELIVERRUNFINISHREQUEST']._serialized_end=1349 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_start=1351 + _globals['_DELIVERRUNFINISHRESPONSE']._serialized_end=1411 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_start=1413 + _globals['_GETOPERATIONSTATSREQUEST']._serialized_end=1459 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_start=1461 + _globals['_GETOPERATIONSTATSRESPONSE']._serialized_end=1575 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_start=1577 + _globals['_CONFIRMRUNFINISHREQUEST']._serialized_end=1622 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_start=1624 + _globals['_CONFIRMRUNFINISHRESPONSE']._serialized_end=1684 + _globals['_CORESERVICE']._serialized_start=1687 + _globals['_CORESERVICE']._serialized_end=2789 # @@protoc_insertion_point(module_scope) diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi index 556813be4..2089c4493 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2.pyi @@ -15,20 +15,16 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class SpinupServiceRequest(_message.Message): - __slots__ = ("owner_token",) - OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] - owner_token: str - def __init__(self, owner_token: _Optional[str] = ...) -> None: ... + __slots__ = () + def __init__(self) -> None: ... class SpinupServiceResponse(_message.Message): __slots__ = () def __init__(self) -> None: ... class TeardownServiceRequest(_message.Message): - __slots__ = ("owner_token",) - OWNER_TOKEN_FIELD_NUMBER: _ClassVar[int] - owner_token: str - def __init__(self, owner_token: _Optional[str] = ...) -> None: ... + __slots__ = () + def __init__(self) -> None: ... class TeardownServiceResponse(_message.Message): __slots__ = () diff --git a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py index 19329c72f..7fe860778 100644 --- a/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py +++ b/swanlab/proto/swanlab/grpc/core/v1/core_pb2_grpc.py @@ -33,7 +33,8 @@ class CoreServiceStub(object): 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; + 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ @@ -107,7 +108,8 @@ class CoreServiceServicer(object): 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; + 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ @@ -120,8 +122,7 @@ def SpinupService(self, request, context): raise NotImplementedError('Method not implemented!') def TeardownService(self, request, context): - """TeardownService 关闭整个服务进程。仅持有 owner token 的调用方(spawn owner)允许执行, - 校验失败返回 PERMISSION_DENIED;与 ConfirmRunFinish 不同,本 RPC 不针对单个 run。 + """TeardownService 关闭整个服务进程。不同于 ConfirmRunFinish ,能连接 core 的 client 都可调用。 """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -184,7 +185,8 @@ def GetOperationStats(self, request, context): raise NotImplementedError('Method not implemented!') def ConfirmRunFinish(self, request, context): - """ConfirmRunFinish 确认指定 run 已排空、资源可释放,但不关闭整个 gRPC Server。 + """ConfirmRunFinish 确认指定 run 已排空并释放其资源,不关闭 gRPC Server。 + 前置条件:该 run 的 DeliverRunFinish 已上传完成,提前调用返回 FAILED_PRECONDITION。 """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -263,7 +265,8 @@ class CoreService(object): 1. Core 与 run 级生命周期严格分离:SpinupService / TeardownService 作用于整个 core 服务进程; 2. 所有 run 级 RPC 通过显式 run_handle 路由到具体会话,不依赖 channel 隐式绑定 run; - 3. ConfirmRunFinish 只确认单个 run 已排空、资源可释放,不会关闭 gRPC Server; + 3. ConfirmRunFinish 确认单个 run 已排空并释放其资源,不关闭 gRPC Server; + 须在对应 run 的 DeliverRunFinish 之后调用,提前调用返回 FAILED_PRECONDITION 且会话保留; 4. GetOperationStats / ConfirmRunFinish 输入使用专用 request,由 run_handle 指定目标 run。 """ From 622666dea2e1739623655132b58e35da82c832f9 Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Thu, 24 Sep 2026 14:10:19 +0800 Subject: [PATCH 14/15] refactor: service architehcture --- core/cmd/swanlab-core/main.go | 38 +- core/internal/manager/manager.go | 99 +++++ core/internal/manager/manager_test.go | 127 +++++++ core/internal/manager/session.go | 45 +++ core/internal/server/controller.go | 21 +- core/internal/server/controller_test.go | 48 +++ core/internal/server/lifecycle.go | 32 +- core/internal/server/lifecycle_test.go | 412 --------------------- core/internal/server/server.go | 163 -------- core/internal/server/server_test.go | 88 ----- core/internal/server/session.go | 118 ------ core/internal/service/core_service.go | 189 ++++++++++ core/internal/service/core_service_test.go | 203 ++++++++++ 13 files changed, 757 insertions(+), 826 deletions(-) create mode 100644 core/internal/manager/manager.go create mode 100644 core/internal/manager/manager_test.go create mode 100644 core/internal/manager/session.go create mode 100644 core/internal/server/controller_test.go delete mode 100644 core/internal/server/lifecycle_test.go delete mode 100644 core/internal/server/server.go delete mode 100644 core/internal/server/server_test.go delete mode 100644 core/internal/server/session.go create mode 100644 core/internal/service/core_service.go create mode 100644 core/internal/service/core_service_test.go diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index cf43b1634..87dce7ee8 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -1,29 +1,19 @@ // Command swanlab-core 是 SwanLab Go core 的进程入口。 // -// 启动约定(Owner Mode): +// 启动与端点约定: // -// swanlab-core --port-filename /core.port --parent-pid -// -// 端点约定: -// -// --listen unix:///path/to/uds Linux/macOS 进程内通信(手动调试入口) +// --port-filename <路径> listen 成功后原子写入端点回报文件(SDK 发现端点依据) +// --parent-pid 监控指定的父进程 PID,父进程退出则 core 联动退出 +// --listen unix:///path/to/uds Linux/macOS 本地通信(手动调试入口) // --listen tcp://127.0.0.1:port Windows 回环地址 -// --port-filename <路径> listen 成功后原子写入端点回报文件(SDK 启动约定) -// -// 未传 --listen 时按平台自选端点:POSIX 使用 port-filename 同目录下的 -// core.sock(UDS,目录需已存在),listen 失败记录 warning 后回退 -// 127.0.0.1 随机回环端口;Windows 使用随机回环端口。 -// -// 信任模型:不使用应用层 token。POSIX 使用 UDS(socket 位于 owner-only -// 私有 runtime 目录),port-file 以 0600 原子发布;loopback TCP 回退 -// 不隔离本机用户,能连接端口的本机进程可调用全部 RPC(含 Teardown)。 // -// --detach 与 --idle-timeout 为 detached 模式预留:detached 未实现, -// 传入时忽略(no-op)并记录 warning,core 仍以 owner 模式运行。 +// 未显式指定 --listen 时按平台自选端点: +// POSIX 优先使用 port-filename 同目录下的 core.sock(UDS),listen 失败时记录 warning 并回退至 127.0.0.1 随机回环端口; +// Windows 默认直接使用随机回环端口。 // -// 生命周期:Teardown RPC、SIGINT/SIGTERM、父进程退出(process 包监控)或 -// Serve 异常汇入 service controller 的关闭路径(GracefulStop → 超时强制 -// Stop);退出时清理自己创建的 socket 文件与 port-file。 +// 生命周期管理: +// 汇集 Teardown RPC、系统信号、父进程退出监控或 Serve 异常,统一触发 Controller 的 +// 关闭序列(优先 GracefulStop,超时强制 Stop);退出时清理自己创建的 socket 与 port-file 文件。 package main import ( @@ -43,10 +33,12 @@ import ( "google.golang.org/grpc" + "github.com/swanhubx/swanlab/core/internal/manager" "github.com/swanhubx/swanlab/core/internal/pkg/console" "github.com/swanhubx/swanlab/core/internal/pkg/portinfo" "github.com/swanhubx/swanlab/core/internal/pkg/process" "github.com/swanhubx/swanlab/core/internal/server" + "github.com/swanhubx/swanlab/core/internal/service" ) // version 与 commit 由构建管线通过 -ldflags -X 注入(见 core/hatch.py), @@ -144,9 +136,13 @@ func run(args []string) int { return exitRunError } + // 装配服务核心分层: + // - server.Controller: 负责 gRPC 进程宿主生命周期与优雅退出控制; + // - manager.Manager: 负责 Run 领域会话管理与路由注册; + // - service.CoreService: 负责 gRPC 请求接入与协议映射。 grpcServer := grpc.NewServer() ctrl := server.NewController(grpcServer, shutdownGrace) - server.NewService(ctrl).Register(grpcServer) + service.NewCoreService(ctrl, manager.New()).Register(grpcServer) // listen 与 server 初始化成功后写 port-file。 if *portFilename != "" { diff --git a/core/internal/manager/manager.go b/core/internal/manager/manager.go new file mode 100644 index 000000000..6a7b05bf8 --- /dev/null +++ b/core/internal/manager/manager.go @@ -0,0 +1,99 @@ +package manager + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "sync" +) + +var ( + // ErrEmptyHandle 表示未提供有效的 run_handle。 + ErrEmptyHandle = errors.New("run_handle must not be empty") + // ErrRunNotFound 表示请求的 run_handle 不存在或已被确认释放。 + ErrRunNotFound = errors.New("unknown or released run_handle") + // ErrRunNotFinished 表示会话尚未标记结束,不能提前确认释放。 + ErrRunNotFinished = errors.New("run finish has not been delivered for this handle") +) + +// Manager 维护 run_handle 到会话的映射注册表,并统一编排 Run 级的生命周期。 +// +// 实现方式: +// 采用读写锁保护 sessions 映射表;所有针对会话的操作与释放均保证并发安全与原子性, +// 杜绝误释放未完成会话或产生竞态问题。 +type Manager struct { + mu sync.RWMutex + sessions map[string]*Session +} + +// New 创建并初始化会话管理器实例。 +func New() *Manager { + return &Manager{sessions: make(map[string]*Session)} +} + +// Start 创建新的 Run 会话,并返回随机生成的安全路由句柄(run_handle)。 +func (m *Manager) Start() (string, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", fmt.Errorf("generate run handle: %w", err) + } + handle := base64.RawURLEncoding.EncodeToString(buf) + + m.mu.Lock() + defer m.mu.Unlock() + if _, exists := m.sessions[handle]; exists { + return "", errors.New("run handle collision") + } + m.sessions[handle] = newSession() + return handle, nil +} + +// Finish 幂等标记指定会话为结束状态。 +// 获取注册表读锁定位会话并在会话锁内更新状态,保证状态变更期间会话不被并发 Confirm 移除。 +func (m *Manager) Finish(handle string) error { + if handle == "" { + return ErrEmptyHandle + } + m.mu.RLock() + defer m.mu.RUnlock() + s, ok := m.sessions[handle] + if !ok { + return ErrRunNotFound + } + s.finish() + return nil +} + +// State 获取指定会话的当前生命周期状态快照。 +func (m *Manager) State(handle string) (RunState, error) { + if handle == "" { + return 0, ErrEmptyHandle + } + m.mu.RLock() + defer m.mu.RUnlock() + s, ok := m.sessions[handle] + if !ok { + return 0, ErrRunNotFound + } + return s.stateSnapshot(), nil +} + +// Confirm 原子校验会话是否已完成并释放会话资源。 +// 若会话尚未完成,则拒绝释放并保留会话,供调用方后续重试。 +func (m *Manager) Confirm(handle string) error { + if handle == "" { + return ErrEmptyHandle + } + m.mu.Lock() + defer m.mu.Unlock() + s, ok := m.sessions[handle] + if !ok { + return ErrRunNotFound + } + if !s.finished() { + return ErrRunNotFinished + } + delete(m.sessions, handle) + return nil +} diff --git a/core/internal/manager/manager_test.go b/core/internal/manager/manager_test.go new file mode 100644 index 000000000..936b4258f --- /dev/null +++ b/core/internal/manager/manager_test.go @@ -0,0 +1,127 @@ +package manager + +import ( + "errors" + "fmt" + "sync" + "testing" +) + +func TestManagerLifecycle(t *testing.T) { + m := New() + handle, err := m.Start() + if err != nil || handle == "" { + t.Fatalf("Start: handle=%q err=%v", handle, err) + } + if state, err := m.State(handle); err != nil || state != RunStateRunning { + t.Fatalf("State after start: state=%v err=%v", state, err) + } + if err := m.Confirm(handle); !errors.Is(err, ErrRunNotFinished) { + t.Fatalf("early Confirm err=%v, want ErrRunNotFinished", err) + } + if err := m.Finish(handle); err != nil { + t.Fatalf("Finish: %v", err) + } + if state, err := m.State(handle); err != nil || state != RunStateFinished { + t.Fatalf("State after finish: state=%v err=%v", state, err) + } + if err := m.Confirm(handle); err != nil { + t.Fatalf("Confirm: %v", err) + } + if _, err := m.State(handle); !errors.Is(err, ErrRunNotFound) { + t.Fatalf("State after confirm err=%v, want ErrRunNotFound", err) + } +} + +func TestFinishConfirmRace(t *testing.T) { + m := New() + for i := 0; i < 20; i++ { + handle, err := m.Start() + if err != nil { + t.Fatal(err) + } + var wg sync.WaitGroup + var finishErr, confirmErr error + wg.Add(2) + go func() { + defer wg.Done() + finishErr = m.Finish(handle) + }() + go func() { + defer wg.Done() + confirmErr = m.Confirm(handle) + }() + wg.Wait() + if finishErr != nil { + t.Fatalf("iteration %d Finish: %v", i, finishErr) + } + if confirmErr != nil && !errors.Is(confirmErr, ErrRunNotFinished) { + t.Fatalf("iteration %d Confirm: %v", i, confirmErr) + } + if confirmErr != nil { + if err := m.Confirm(handle); err != nil { + t.Fatalf("iteration %d retry Confirm: %v", i, err) + } + } + } +} + +func TestRunIsolation(t *testing.T) { + m := New() + handleA, err := m.Start() + if err != nil { + t.Fatal(err) + } + handleB, err := m.Start() + if err != nil { + t.Fatal(err) + } + if handleA == handleB { + t.Fatal("two runs must receive distinct handles") + } + if err := m.Finish(handleA); err != nil { + t.Fatal(err) + } + if state, _ := m.State(handleA); state != RunStateFinished { + t.Fatalf("run A state=%v, want Finished", state) + } + if state, _ := m.State(handleB); state != RunStateRunning { + t.Fatalf("run B state=%v, want Running", state) + } +} + +func TestConcurrentRuns(t *testing.T) { + m := New() + const clients = 4 + var wg sync.WaitGroup + errs := make(chan error, clients) + for i := 0; i < clients; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + handle, err := m.Start() + if err != nil { + errs <- fmt.Errorf("client %d start: %w", id, err) + return + } + for j := 0; j < 20; j++ { + if _, err = m.State(handle); err != nil { + errs <- fmt.Errorf("client %d state: %w", id, err) + return + } + } + if err = m.Finish(handle); err != nil { + errs <- fmt.Errorf("client %d finish: %w", id, err) + return + } + if err = m.Confirm(handle); err != nil { + errs <- fmt.Errorf("client %d confirm: %w", id, err) + } + }(i) + } + wg.Wait() + close(errs) + for err := range errs { + t.Error(err) + } +} diff --git a/core/internal/manager/session.go b/core/internal/manager/session.go new file mode 100644 index 000000000..00aec8fcf --- /dev/null +++ b/core/internal/manager/session.go @@ -0,0 +1,45 @@ +// Package manager 负责维护 swanlab-core 的 Run 会话与业务编排逻辑。 +// +// 该包与具体传输协议无关(不依赖 gRPC/Protobuf),专职管理 Run 级状态机与会话路由。 +package manager + +import "sync" + +// RunState 表示单个实验会话的内部生命周期状态。 +type RunState int32 + +const ( + // RunStateRunning 会话正在运行中。 + RunStateRunning RunState = iota + // RunStateFinished 会话已标记完成。 + RunStateFinished +) + +// Session 保存单个 run_handle 对应的会话状态与生命周期数据。 +type Session struct { + mu sync.Mutex + state RunState +} + +func newSession() *Session { + return &Session{state: RunStateRunning} +} + +// finish 幂等标记会话已结束。 +func (s *Session) finish() { + s.mu.Lock() + defer s.mu.Unlock() + s.state = RunStateFinished +} + +// stateSnapshot 返回当前会话状态的并发安全快照。 +func (s *Session) stateSnapshot() RunState { + s.mu.Lock() + defer s.mu.Unlock() + return s.state +} + +// finished 检查会话是否已完成。 +func (s *Session) finished() bool { + return s.stateSnapshot() == RunStateFinished +} diff --git a/core/internal/server/controller.go b/core/internal/server/controller.go index 30267215e..bafe98f72 100644 --- a/core/internal/server/controller.go +++ b/core/internal/server/controller.go @@ -1,3 +1,8 @@ +// Package server 负责管理本地 gRPC 服务的宿主环境与生命周期。 +// +// 职责: +// - 管理服务级生命周期状态(NotReady、Ready、Stopping、Closed); +// - 统一仲裁服务退出流程,支持优雅退出与超时兜底强制关闭。 package server import ( @@ -9,12 +14,12 @@ import ( "github.com/swanhubx/swanlab/core/internal/pkg/console" ) -// Controller 仲裁服务的统一关闭路径,并持有服务状态机。 +// Controller 负责协调服务的统一关闭路径,并管理服务生命周期。 // -// Teardown RPC、SIGINT/SIGTERM、父进程退出通知与 Serve 异常都汇入同一条 -// 收尾序列:先 GracefulStop 等待在途请求完成,超过 grace 时限后强制 Stop, -// 并保证 Serve 一定返回。Shutdown 幂等,多次触发只执行一次。 -// 进入收尾时服务状态转为 STOPPING,收尾完成后转为 CLOSED。 +// 实现方式: +// 汇集 Teardown RPC、系统信号、父进程退出及 Serve 异常等所有退出来源; +// 通过 sync.Once 保证幂等执行:先触发 GracefulStop 尝试优雅退出, +// 超时后强制调用 Stop 兜底,确保进程可靠结束。 type Controller struct { server *grpc.Server grace time.Duration @@ -23,7 +28,7 @@ type Controller struct { lc *Lifecycle } -// NewController 包装一个 gRPC Server,grace 为优雅关闭的等待上限。 +// NewController 创建服务控制器,grace 为优雅退出的等待上限。 func NewController(g *grpc.Server, grace time.Duration) *Controller { return &Controller{ server: g, @@ -33,12 +38,12 @@ func NewController(g *grpc.Server, grace time.Duration) *Controller { } } -// Lifecycle 返回由本 controller 仲裁的服务状态机。 +// Lifecycle 返回由本控制器管理的服务生命周期状态机。 func (c *Controller) Lifecycle() *Lifecycle { return c.lc } -// Shutdown 幂等触发关闭;cause 仅用于日志,标识关闭来源。 +// Shutdown 触发服务关闭流程,操作具备幂等性;cause 仅用于日志标识退出来源。 func (c *Controller) Shutdown(cause string) { c.once.Do(func() { c.lc.BeginStopping() diff --git a/core/internal/server/controller_test.go b/core/internal/server/controller_test.go new file mode 100644 index 000000000..8fc30914c --- /dev/null +++ b/core/internal/server/controller_test.go @@ -0,0 +1,48 @@ +package server + +import ( + "testing" + "time" + + "google.golang.org/grpc" +) + +const testGrace = 2 * time.Second + +func TestControllerShutdownIdempotent(t *testing.T) { + g := grpc.NewServer() + ctrl := NewController(g, testGrace) + ctrl.Shutdown("first") + ctrl.Shutdown("second") + select { + case <-ctrl.Done(): + case <-time.After(2 * time.Second): + t.Fatal("controller Done not closed after Shutdown") + } + <-ctrl.Done() + if st := ctrl.Lifecycle().Get(); st != StateClosed { + t.Fatalf("lifecycle state after shutdown = %v, want StateClosed", st) + } +} + +func TestLifecycleStateTransitions(t *testing.T) { + lc := newLifecycle() + if lc.Get() != StateNotReady { + t.Fatalf("initial state = %v, want StateNotReady", lc.Get()) + } + if !lc.Spinup() || lc.Get() != StateReady { + t.Fatalf("spinup state = %v, want StateReady", lc.Get()) + } + if !lc.Spinup() { + t.Fatal("repeated Spinup in READY must succeed") + } + lc.BeginStopping() + if lc.Get() != StateStopping || lc.Spinup() { + t.Fatalf("stopping state = %v, Spinup must be rejected", lc.Get()) + } + lc.Close() + lc.Close() + if lc.Get() != StateClosed { + t.Fatalf("closed state = %v, want StateClosed", lc.Get()) + } +} diff --git a/core/internal/server/lifecycle.go b/core/internal/server/lifecycle.go index 76aee20b2..9451d8e1c 100644 --- a/core/internal/server/lifecycle.go +++ b/core/internal/server/lifecycle.go @@ -2,26 +2,26 @@ package server import "sync" -// ServiceState 是服务级生命周期状态,只存在于 Go 内存,不上 proto。 -// 线上表达是 gRPC 状态码:READY 之前的 run RPC 返回 FAILED_PRECONDITION, -// 关闭仲裁由 Controller 汇入统一关闭路径。 +// ServiceState 表示 gRPC 服务的进程级生命周期状态。 type ServiceState int32 const ( - // StateNotReady 服务已监听但未完成 Spinup,拒绝 run 级 RPC。 + // StateNotReady 服务已监听但尚未完成握手(SpinupService),拒绝业务 RPC。 StateNotReady ServiceState = iota - // StateReady SpinupService 成功,接受 run 级 RPC。 + // StateReady 服务已就绪,可正常接受和处理业务请求。 StateReady - // StateStopping 关闭序列进行中(Teardown/信号/父死/serve 错误)。 + // StateStopping 服务正在执行退出流程。 StateStopping - // StateClosed 关闭序列完成。 + // StateClosed 服务已完全关闭。 StateClosed ) -// Lifecycle 是服务状态机的并发安全实现。 +// Lifecycle 维护并发安全的服务生命周期状态机。 // -// 转换:NOT_READY → READY(SpinupService,READY 下幂等); -// NOT_READY/READY → STOPPING(Controller.Shutdown,幂等);STOPPING → CLOSED(关闭完成)。 +// 状态流转: +// - StateNotReady -> StateReady:通过 Spinup 触发,Ready 状态下重复调用幂等成功; +// - StateNotReady / StateReady -> StateStopping:服务开始关闭(Shutdown)时进入; +// - StateStopping -> StateClosed:服务收尾完成后进入。 type Lifecycle struct { mu sync.RWMutex state ServiceState @@ -31,20 +31,20 @@ func newLifecycle() *Lifecycle { return &Lifecycle{state: StateNotReady} } -// Get 返回当前服务状态。 +// Get 返回当前的服务生命周期状态。 func (l *Lifecycle) Get() ServiceState { l.mu.RLock() defer l.mu.RUnlock() return l.state } -// Ready 报告服务是否处于 READY。 +// Ready 检查服务当前是否处于就绪(StateReady)状态。 func (l *Lifecycle) Ready() bool { return l.Get() == StateReady } -// Spinup 幂等执行 NOT_READY → READY;READY 下重复调用成功; -// 已进入 STOPPING/CLOSED 时返回 false,由调用方映射为 FAILED_PRECONDITION。 +// Spinup 将服务状态置为 StateReady。 +// 在 StateReady 状态下重复调用幂等返回 true;若服务已处于退出或关闭中,则返回 false。 func (l *Lifecycle) Spinup() bool { l.mu.Lock() defer l.mu.Unlock() @@ -59,7 +59,7 @@ func (l *Lifecycle) Spinup() bool { } } -// BeginStopping 幂等进入 STOPPING;已在 STOPPING/CLOSED 时为 no-op。 +// BeginStopping 将服务置为 StateStopping 退出状态,多次调用幂等。 func (l *Lifecycle) BeginStopping() { l.mu.Lock() defer l.mu.Unlock() @@ -68,7 +68,7 @@ func (l *Lifecycle) BeginStopping() { } } -// Close 在关闭序列完成后进入 CLOSED;幂等。 +// Close 将服务置为 StateClosed 状态,标识服务已完全关闭。 func (l *Lifecycle) Close() { l.mu.Lock() defer l.mu.Unlock() diff --git a/core/internal/server/lifecycle_test.go b/core/internal/server/lifecycle_test.go deleted file mode 100644 index 3c328e825..000000000 --- a/core/internal/server/lifecycle_test.go +++ /dev/null @@ -1,412 +0,0 @@ -package server - -import ( - "fmt" - "sync" - "testing" - "time" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" - operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" - runv1 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" -) - -// spinup 是测试辅助:把服务置为 READY。 -func spinup(t *testing.T, env *testEnv) { - t.Helper() - if _, err := env.client.SpinupService(callCtx(t), &corev1.SpinupServiceRequest{}); err != nil { - t.Fatalf("SpinupService: %v", err) - } -} - -// startRun 是测试辅助:创建一个 run 会话并返回 run_handle。 -func startRun(t *testing.T, env *testEnv) string { - t.Helper() - resp, err := env.client.DeliverRunStart( - callCtx(t), - &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}, - ) - if err != nil { - t.Fatalf("DeliverRunStart: %v", err) - } - if !resp.GetSuccess() { - t.Fatal("DeliverRunStart success = false") - } - if resp.GetRunHandle() == "" { - t.Fatal("DeliverRunStart returned empty run_handle") - } - return resp.GetRunHandle() -} - -func TestLifecycleStateTransitions(t *testing.T) { - lc := newLifecycle() - if lc.Get() != StateNotReady { - t.Fatalf("initial state = %v, want StateNotReady", lc.Get()) - } - if !lc.Spinup() { - t.Fatal("Spinup from NOT_READY must succeed") - } - if lc.Get() != StateReady { - t.Fatalf("state after spinup = %v, want StateReady", lc.Get()) - } - if !lc.Spinup() { - t.Fatal("repeated Spinup in READY must succeed (idempotent)") - } - lc.BeginStopping() - if lc.Get() != StateStopping { - t.Fatalf("state after BeginStopping = %v, want StateStopping", lc.Get()) - } - lc.BeginStopping() // 幂等 - if lc.Spinup() { - t.Fatal("Spinup in STOPPING must be rejected") - } - lc.Close() - if lc.Get() != StateClosed { - t.Fatalf("state after Close = %v, want StateClosed", lc.Get()) - } - lc.Close() // 幂等 -} - -func TestSpinupIdempotent(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - spinup(t, env) // 重复 Spinup 在 READY 下幂等成功 -} - -func TestRunRPCsRejectedBeforeReady(t *testing.T) { - env := newTestEnv(t) - ctx := callCtx(t) - checks := []struct { - name string - call func() error - }{ - { - "DeliverRunStart", - func() error { - _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) - return err - }, - }, - { - "UpsertScalars", - func() error { - _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: "any"}) - return err - }, - }, - { - "GetOperationStats", - func() error { - _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "any"}) - return err - }, - }, - { - "DeliverRunFinish", - func() error { - _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: "any"}) - return err - }, - }, - { - "ConfirmRunFinish", - func() error { - _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: "any"}) - return err - }, - }, - } - for _, tc := range checks { - if err := tc.call(); status.Code(err) != codes.FailedPrecondition { - t.Fatalf("%s before ready: err = %v, want FailedPrecondition", tc.name, err) - } - } -} - -func TestUpsertsUnimplementedAfterReady(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - handle := startRun(t, env) - ctx := callCtx(t) - for _, tc := range []struct { - name string - call func() error - }{ - { - "UpsertColumns", - func() error { - _, err := env.client.UpsertColumns(ctx, &corev1.UpsertColumnsRequest{RunHandle: handle}) - return err - }, - }, - { - "UpsertScalars", - func() error { - _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: handle}) - return err - }, - }, - { - "UpsertMedia", - func() error { - _, err := env.client.UpsertMedia(ctx, &corev1.UpsertMediaRequest{RunHandle: handle}) - return err - }, - }, - { - "UpsertLogs", - func() error { - _, err := env.client.UpsertLogs(ctx, &corev1.UpsertLogsRequest{RunHandle: handle}) - return err - }, - }, - { - "UpsertSaves", - func() error { - _, err := env.client.UpsertSaves(ctx, &corev1.UpsertSavesRequest{RunHandle: handle}) - return err - }, - }, - } { - if err := tc.call(); status.Code(err) != codes.Unimplemented { - t.Fatalf("%s after ready: err = %v, want Unimplemented", tc.name, err) - } - } -} - -func TestDeliverRunStartRequiresStartRecord(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - _, err := env.client.DeliverRunStart(callCtx(t), &corev1.DeliverRunStartRequest{}) - if status.Code(err) != codes.InvalidArgument { - t.Fatalf("DeliverRunStart without start_record: err = %v, want InvalidArgument", err) - } -} - -func TestHandleValidation(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - ctx := callCtx(t) - - // 空 handle:InvalidArgument - if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{}); status.Code(err) != codes.InvalidArgument { - t.Fatalf("empty handle stats err = %v, want InvalidArgument", err) - } - // 未知 handle:NotFound - if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "no-such-handle"}); status.Code(err) != codes.NotFound { - t.Fatalf("unknown handle stats err = %v, want NotFound", err) - } -} - -func TestRunLifecycleCoreStateMapping(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - handle := startRun(t, env) - ctx := callCtx(t) - - // start 后:RUNNING - resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) - if err != nil || !resp.GetSuccess() { - t.Fatalf("stats before finish: err = %v, resp = %v", err, resp) - } - if resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_RUNNING { - t.Fatalf("stats state before finish = %v, want RUNNING", resp.GetStats().GetState()) - } - - // finish 后:FINISHED(骨架无数据可排空) - if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { - t.Fatalf("DeliverRunFinish: %v", err) - } - resp, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) - if err != nil { - t.Fatalf("stats after finish: %v", err) - } - if resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { - t.Fatalf("stats state after finish = %v, want FINISHED", resp.GetStats().GetState()) - } - - // confirm:释放会话,不关闭 server - if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { - t.Fatalf("ConfirmRunFinish: %v", err) - } - select { - case <-env.ctrl.Done(): - t.Fatal("ConfirmRunFinish must not shut down the server") - case <-afterWindow(): - } - // confirm 后 handle 失效 - if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { - t.Fatalf("stats after confirm err = %v, want NotFound", err) - } - if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { - t.Fatalf("repeat confirm err = %v, want NotFound", err) - } -} - -// TestConfirmBeforeFinishRejected 验证 ConfirmRunFinish 的前置条件: -// finish 未交付时 confirm 返回 FailedPrecondition 且会话保留, -// 之后可完成 finish/confirm 流程。 -func TestConfirmBeforeFinishRejected(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - handle := startRun(t, env) - ctx := callCtx(t) - - if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); status.Code(err) != codes.FailedPrecondition { - t.Fatalf("confirm before finish err = %v, want FailedPrecondition", err) - } - // 会话未被摘除:stats 报 RUNNING - resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) - if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_RUNNING { - t.Fatalf("stats after rejected confirm: err = %v, state = %v, want RUNNING", err, resp.GetStats().GetState()) - } - // finish 后 confirm 成功,会话被释放 - if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { - t.Fatalf("DeliverRunFinish: %v", err) - } - if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { - t.Fatalf("ConfirmRunFinish after finish: %v", err) - } - if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { - t.Fatalf("stats after confirm err = %v, want NotFound", err) - } -} - -// TestFinishConfirmRace 并发执行 finish 与 confirm,验证 registry 的 -// 原子校验:confirm 在 finish 标记后才会摘除会话,因此 finish 必然成功; -// confirm 的合法结果为成功(后于 finish)或 FailedPrecondition(先于 finish)。 -func TestFinishConfirmRace(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - ctx := callCtx(t) - - for i := 0; i < 20; i++ { - handle := startRun(t, env) - var wg sync.WaitGroup - var finishErr, confirmErr error - wg.Add(2) - go func() { - defer wg.Done() - _, finishErr = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}) - }() - go func() { - defer wg.Done() - _, confirmErr = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}) - }() - wg.Wait() - - if finishErr != nil { - t.Fatalf("iter %d: finish must always succeed (session must survive a losing confirm): %v", i, finishErr) - } - if confirmErr != nil && status.Code(confirmErr) != codes.FailedPrecondition { - t.Fatalf("iter %d: confirm err = %v, want nil or FailedPrecondition", i, confirmErr) - } - // confirm 输了竞态时会话未释放,补一次 confirm - if confirmErr != nil { - if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { - t.Fatalf("iter %d: retry confirm after finish: %v", i, err) - } - } - if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { - t.Fatalf("iter %d: stats after release err = %v, want NotFound", i, err) - } - } -} - -func TestTwoRunHandleIsolation(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - handleA, handleB := startRun(t, env), startRun(t, env) - if handleA == handleB { - t.Fatal("two runs must receive distinct run_handles") - } - ctx := callCtx(t) - - // 结束 A:A 报 FINISHED,B 保持 RUNNING,互不串扰 - if _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handleA}); err != nil { - t.Fatalf("finish A: %v", err) - } - for _, tc := range []struct { - handle string - want operationv1.CoreState - }{ - {handleA, operationv1.CoreState_CORE_STATE_FINISHED}, - {handleB, operationv1.CoreState_CORE_STATE_RUNNING}, - } { - resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: tc.handle}) - if err != nil { - t.Fatalf("stats %s: %v", tc.handle, err) - } - if resp.GetStats().GetState() != tc.want { - t.Fatalf("stats %s state = %v, want %v", tc.handle, resp.GetStats().GetState(), tc.want) - } - } - - // confirm A:B 可正常走完生命周期 - if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handleA}); err != nil { - t.Fatalf("confirm A: %v", err) - } - if _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handleB}); err != nil { - t.Fatalf("finish B: %v", err) - } - resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handleB}) - if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { - t.Fatalf("stats B after finish: err = %v, state = %v", err, resp.GetStats().GetState()) - } -} - -// TestConcurrentRunSessions 模拟两个并发 client 各自跑完生命周期, -// 配合 -race 验证 session registry 的并发安全与会话隔离。 -func TestConcurrentRunSessions(t *testing.T) { - env := newTestEnv(t) - spinup(t, env) - - const clients = 4 - var wg sync.WaitGroup - errs := make(chan error, clients) - for i := 0; i < clients; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - ctx := callCtx(t) - resp, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) - if err != nil { - errs <- fmt.Errorf("client %d start: %w", id, err) - return - } - handle := resp.GetRunHandle() - for r := 0; r < 20; r++ { - if _, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); err != nil { - errs <- fmt.Errorf("client %d stats: %w", id, err) - return - } - } - if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { - errs <- fmt.Errorf("client %d finish: %w", id, err) - return - } - statsResp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) - if err != nil || statsResp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { - errs <- fmt.Errorf("client %d stats after finish: err = %v, state = %v", id, err, statsResp.GetStats().GetState()) - return - } - if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { - errs <- fmt.Errorf("client %d confirm: %w", id, err) - return - } - }(i) - } - wg.Wait() - close(errs) - for err := range errs { - t.Error(err) - } -} - -// afterWindow 返回 noShutdownWindow 时长通道,用于断言"未触发关闭"。 -func afterWindow() <-chan time.Time { - return time.After(noShutdownWindow) -} diff --git a/core/internal/server/server.go b/core/internal/server/server.go deleted file mode 100644 index 775bd106d..000000000 --- a/core/internal/server/server.go +++ /dev/null @@ -1,163 +0,0 @@ -// Package server 提供 swanlab-core 的 gRPC 服务端实现。 -// -// 生命周期(骨架): -// - 不使用应用层 token;信任边界是本地 transport 与文件系统权限 -// (UDS socket、owner-only runtime 目录、0600 port-file),能连接端点的 -// client 可调用全部 RPC,包括 Teardown; -// - SpinupService 是幂等的 READY 屏障; -// - 五类 Upsert* 在 store/transport 实现前返回 UNIMPLEMENTED; -// - DeliverRunStart 创建 run_handle 会话,finish/stats/confirm 按 handle 路由; -// - ConfirmRunFinish 在 finish 交付后原子释放对应会话,不关闭 gRPC Server; -// - TeardownService 触发 controller 的关闭路径。 -package server - -import ( - "context" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" - - corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" - operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" -) - -// Service 实现 CoreService。 -// -// 除 Spinup/Teardown 与 run 生命周期 RPC 外,五类业务 upsert 由嵌入的 -// Unimplemented 改写为 UNIMPLEMENTED(READY 门控之后)。 -type Service struct { - corev1.UnimplementedCoreServiceServer - - controller *Controller - lc *Lifecycle - registry *sessionRegistry -} - -// NewService 创建服务实例。 -func NewService(controller *Controller) *Service { - return &Service{ - controller: controller, - lc: controller.Lifecycle(), - registry: newSessionRegistry(), - } -} - -// Register 将服务注册到 gRPC Server。 -func (s *Service) Register(g *grpc.Server) { - corev1.RegisterCoreServiceServer(g, s) -} - -// requireReady 拒绝 READY 之前的 run 级 RPC。 -func (s *Service) requireReady() error { - if s.lc.Ready() { - return nil - } - return status.Error(codes.FailedPrecondition, "service is not ready; call SpinupService first") -} - -// rejectUpsert 是五类 upsert 的应答:READY 门控通过后返回 -// UNIMPLEMENTED——store/transport 未实现前不得用 Empty 成功假接收。 -func (s *Service) rejectUpsert() error { - if err := s.requireReady(); err != nil { - return err - } - return status.Error(codes.Unimplemented, "upsert data path is not implemented in this core build") -} - -// SpinupService 把服务置为 READY,幂等;STOPPING/CLOSED 下拒绝。 -func (s *Service) SpinupService(_ context.Context, _ *corev1.SpinupServiceRequest) (*corev1.SpinupServiceResponse, error) { - if !s.lc.Spinup() { - return nil, status.Error(codes.FailedPrecondition, "service is stopping or closed") - } - return &corev1.SpinupServiceResponse{}, nil -} - -// TeardownService 关闭整个服务进程。无 owner 校验:能连接端点的 client -// 都可调用,SDK 约定由 owner 发起。异步触发关闭路径,响应先于连接关闭送达。 -func (s *Service) TeardownService(_ context.Context, _ *corev1.TeardownServiceRequest) (*corev1.TeardownServiceResponse, error) { - s.controller.Shutdown("teardown") - return &corev1.TeardownServiceResponse{}, nil -} - -// DeliverRunStart 创建 run 会话并返回非空 opaque run_handle。 -func (s *Service) DeliverRunStart(_ context.Context, req *corev1.DeliverRunStartRequest) (*corev1.DeliverRunStartResponse, error) { - if err := s.requireReady(); err != nil { - return nil, err - } - if req.GetStartRecord() == nil { - return nil, status.Error(codes.InvalidArgument, "start_record must not be nil") - } - handle, _, err := s.registry.create() - if err != nil { - return nil, err - } - // 骨架:回显最小有效结果,不落盘、不上传、不访问云端。 - return &corev1.DeliverRunStartResponse{ - Success: true, - Run: req.GetStartRecord(), - NewExperiment: true, - RunHandle: handle, - }, nil -} - -func (s *Service) UpsertColumns(_ context.Context, _ *corev1.UpsertColumnsRequest) (*emptypb.Empty, error) { - return nil, s.rejectUpsert() -} - -func (s *Service) UpsertScalars(_ context.Context, _ *corev1.UpsertScalarsRequest) (*emptypb.Empty, error) { - return nil, s.rejectUpsert() -} - -func (s *Service) UpsertMedia(_ context.Context, _ *corev1.UpsertMediaRequest) (*emptypb.Empty, error) { - return nil, s.rejectUpsert() -} - -func (s *Service) UpsertLogs(_ context.Context, _ *corev1.UpsertLogsRequest) (*emptypb.Empty, error) { - return nil, s.rejectUpsert() -} - -func (s *Service) UpsertSaves(_ context.Context, _ *corev1.UpsertSavesRequest) (*emptypb.Empty, error) { - return nil, s.rejectUpsert() -} - -// DeliverRunFinish 结束对应会话的 run,服务保持存活。 -func (s *Service) DeliverRunFinish(_ context.Context, req *corev1.DeliverRunFinishRequest) (*corev1.DeliverRunFinishResponse, error) { - if err := s.requireReady(); err != nil { - return nil, err - } - session, err := s.registry.lookup(req.GetRunHandle()) - if err != nil { - return nil, err - } - session.finish() - return &corev1.DeliverRunFinishResponse{Success: true}, nil -} - -// GetOperationStats 返回对应会话的 CoreState 映射(run 级数据排空轴)。 -func (s *Service) GetOperationStats(_ context.Context, req *corev1.GetOperationStatsRequest) (*corev1.GetOperationStatsResponse, error) { - if err := s.requireReady(); err != nil { - return nil, err - } - session, err := s.registry.lookup(req.GetRunHandle()) - if err != nil { - return nil, err - } - return &corev1.GetOperationStatsResponse{ - Success: true, - Stats: &operationv1.OperationStats{State: session.coreState()}, - }, nil -} - -// ConfirmRunFinish 确认对应会话已排空并原子释放资源,不关闭 gRPC Server。 -// finish 未交付时返回 FAILED_PRECONDITION,会话保留。 -func (s *Service) ConfirmRunFinish(_ context.Context, req *corev1.ConfirmRunFinishRequest) (*corev1.ConfirmRunFinishResponse, error) { - if err := s.requireReady(); err != nil { - return nil, err - } - if err := s.registry.confirm(req.GetRunHandle()); err != nil { - return nil, err - } - return &corev1.ConfirmRunFinishResponse{Success: true}, nil -} diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go deleted file mode 100644 index 623044efb..000000000 --- a/core/internal/server/server_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package server - -import ( - "context" - "net" - "testing" - "time" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" - - corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" -) - -const ( - bufconnSize = 1 << 20 - testGrace = 2 * time.Second - noShutdownWindow = 200 * time.Millisecond - callTimeout = 2 * time.Second -) - -type testEnv struct { - client corev1.CoreServiceClient - ctrl *Controller -} - -// newTestEnv 在内存连接上启动完整服务端,返回客户端句柄与关闭控制器。 -func newTestEnv(t *testing.T) *testEnv { - t.Helper() - g := grpc.NewServer() - ctrl := NewController(g, testGrace) - NewService(ctrl).Register(g) - lis := bufconn.Listen(bufconnSize) - go func() { _ = g.Serve(lis) }() - conn, err := grpc.NewClient("passthrough:///bufnet", - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { - return lis.DialContext(context.Background()) - }), - ) - if err != nil { - t.Fatalf("grpc.NewClient: %v", err) - } - t.Cleanup(func() { - _ = conn.Close() - ctrl.Shutdown("test-cleanup") - <-ctrl.Done() - }) - return &testEnv{client: corev1.NewCoreServiceClient(conn), ctrl: ctrl} -} - -// callCtx 返回带超时的 context。 -func callCtx(t *testing.T) context.Context { - t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), callTimeout) - t.Cleanup(cancel) - return ctx -} - -func TestTeardownServiceShutsDownServer(t *testing.T) { - env := newTestEnv(t) - if _, err := env.client.TeardownService(callCtx(t), &corev1.TeardownServiceRequest{}); err != nil { - t.Fatalf("TeardownService: %v", err) - } - select { - case <-env.ctrl.Done(): - case <-time.After(callTimeout): - t.Fatal("shutdown not completed after valid teardown") - } -} - -func TestControllerShutdownIdempotent(t *testing.T) { - g := grpc.NewServer() - ctrl := NewController(g, testGrace) - ctrl.Shutdown("first") - ctrl.Shutdown("second") - select { - case <-ctrl.Done(): - case <-time.After(callTimeout): - t.Fatal("controller Done not closed after Shutdown") - } - // 重复读取已关闭的 Done 不应阻塞或 panic - <-ctrl.Done() - if st := ctrl.Lifecycle().Get(); st != StateClosed { - t.Fatalf("lifecycle state after shutdown = %v, want StateClosed", st) - } -} diff --git a/core/internal/server/session.go b/core/internal/server/session.go deleted file mode 100644 index 0c71cb394..000000000 --- a/core/internal/server/session.go +++ /dev/null @@ -1,118 +0,0 @@ -package server - -import ( - "crypto/rand" - "encoding/base64" - "sync" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" -) - -// runSessionState 是单个 run 会话的内部状态(不上 proto、不并入 CoreState): -// running → finishStaged(DeliverRunFinish)→ confirmed/释放(ConfirmRunFinish)。 -type runSessionState int32 - -const ( - sessionRunning runSessionState = iota - sessionFinishStaged -) - -// runSession 记录单个 run 会话的路由状态。 -type runSession struct { - mu sync.Mutex - state runSessionState -} - -// finish 幂等标记 finish 已交付。 -func (s *runSession) finish() { - s.mu.Lock() - defer s.mu.Unlock() - s.state = sessionFinishStaged -} - -// finished 报告 finish 是否完成 record 上传 -// ConfirmRunFinish 的前置条件 -func (s *runSession) finished() bool { - s.mu.Lock() - defer s.mu.Unlock() - return s.state == sessionFinishStaged -} - -// coreState 把内部会话状态映射为线上契约 CoreState(run 级数据排空轴)。 -// finish 交付后骨架无数据可排空,直接报 FINISHED,drain 轮询即可判停。 -func (s *runSession) coreState() operationv1.CoreState { - s.mu.Lock() - defer s.mu.Unlock() - if s.state == sessionRunning { - return operationv1.CoreState_CORE_STATE_RUNNING - } - return operationv1.CoreState_CORE_STATE_FINISHED -} - -// sessionRegistry 是并发安全的 run_handle → 会话映射。 -// DeliverRunStart 创建,ConfirmRunFinish 释放;confirm 绝不关闭 gRPC Server。 -type sessionRegistry struct { - mu sync.RWMutex - sessions map[string]*runSession -} - -func newSessionRegistry() *sessionRegistry { - return &sessionRegistry{sessions: make(map[string]*runSession)} -} - -// lookup 校验并返回目标会话;空 handle 为 InvalidArgument, -// 未知或已释放的 handle 为 NotFound。 -func (r *sessionRegistry) lookup(handle string) (*runSession, error) { - if handle == "" { - return nil, status.Error(codes.InvalidArgument, "run_handle must not be empty") - } - r.mu.RLock() - defer r.mu.RUnlock() - s, ok := r.sessions[handle] - if !ok { - return nil, status.Error(codes.NotFound, "unknown or released run_handle") - } - return s, nil -} - -// create 生成随机 opaque handle 并登记会话。 -func (r *sessionRegistry) create() (string, *runSession, error) { - buf := make([]byte, 32) - if _, err := rand.Read(buf); err != nil { - return "", nil, status.Error(codes.Internal, "generate run handle") - } - handle := base64.RawURLEncoding.EncodeToString(buf) - s := &runSession{} - r.mu.Lock() - defer r.mu.Unlock() - if _, exists := r.sessions[handle]; exists { - // 256-bit 随机碰撞概率可忽略;出现即视为内部错误。 - return "", nil, status.Error(codes.Internal, "run handle collision") - } - r.sessions[handle] = s - return handle, s, nil -} - -// confirm 在 registry 锁下原子校验并摘除会话:handle 为空返回 -// InvalidArgument,未知或已释放返回 NotFound,finish 未交付返回 -// FailedPrecondition 且会话保留。confirm 成功时 finish 已生效。 -// 锁顺序:registry mu → session mu。 -func (r *sessionRegistry) confirm(handle string) error { - if handle == "" { - return status.Error(codes.InvalidArgument, "run_handle must not be empty") - } - r.mu.Lock() - defer r.mu.Unlock() - s, ok := r.sessions[handle] - if !ok { - return status.Error(codes.NotFound, "unknown or released run_handle") - } - if !s.finished() { - return status.Error(codes.FailedPrecondition, "run finish has not been delivered for this handle") - } - delete(r.sessions, handle) - return nil -} diff --git a/core/internal/service/core_service.go b/core/internal/service/core_service.go new file mode 100644 index 000000000..b2bafa8e8 --- /dev/null +++ b/core/internal/service/core_service.go @@ -0,0 +1,189 @@ +// Package service 提供 swanlab-core 的 gRPC 协议接入层实现。 +// +// 职责: +// - 实现 gRPC 契约(CoreServiceServer),校验并解析 Protobuf 请求; +// - 拦截未就绪服务的业务调用,前置统一做状态检查; +// - 将内部领域错误(manager)映射为标准的 gRPC 状态码(InvalidArgument、NotFound 等); +// - 将服务退出指令委派给 internal/server 控制器执行。 +package service + +import ( + "context" + "errors" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/swanhubx/swanlab/core/internal/manager" + "github.com/swanhubx/swanlab/core/internal/server" + corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" + operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" +) + +// CoreService 实现生成的 CoreServiceServer 契约,作为 gRPC 请求的接入网关。 +type CoreService struct { + corev1.UnimplementedCoreServiceServer + + controller *server.Controller + lifecycle *server.Lifecycle + manager *manager.Manager +} + +// NewCoreService 创建 gRPC 接入服务实例,关联服务生命周期控制器与 Run 会话管理器。 +func NewCoreService(controller *server.Controller, runManager *manager.Manager) *CoreService { + return &CoreService{ + controller: controller, + lifecycle: controller.Lifecycle(), + manager: runManager, + } +} + +// Register 将当前服务注册到指定的 gRPC Server。 +func (s *CoreService) Register(g *grpc.Server) { + corev1.RegisterCoreServiceServer(g, s) +} + +// requireReady 检查服务是否处于就绪状态,未就绪时返回 FailedPrecondition 错误。 +func (s *CoreService) requireReady() error { + if s.lifecycle.Ready() { + return nil + } + return status.Error(codes.FailedPrecondition, "service is not ready; call SpinupService first") +} + +// rejectUpsert 统一拦截数据上传 RPC,在底层存储与传输通道接入前明确返回 Unimplemented。 +func (s *CoreService) rejectUpsert() error { + if err := s.requireReady(); err != nil { + return err + } + return status.Error(codes.Unimplemented, "upsert data path is not implemented in this core build") +} + +// SpinupService 执行服务启动握手,将服务状态置为就绪(Ready)。 +func (s *CoreService) SpinupService( + _ context.Context, + _ *corev1.SpinupServiceRequest, +) (*corev1.SpinupServiceResponse, error) { + if !s.lifecycle.Spinup() { + return nil, status.Error(codes.FailedPrecondition, "service is stopping or closed") + } + return &corev1.SpinupServiceResponse{}, nil +} + +// TeardownService 请求关闭整个 Core 服务,异步触发控制器的关闭流程。 +func (s *CoreService) TeardownService( + _ context.Context, + _ *corev1.TeardownServiceRequest, +) (*corev1.TeardownServiceResponse, error) { + s.controller.Shutdown("teardown") + return &corev1.TeardownServiceResponse{}, nil +} + +// DeliverRunStart 接收并校验启动记录,创建新的 Run 会话并返回对应的 run_handle。 +func (s *CoreService) DeliverRunStart( + _ context.Context, + req *corev1.DeliverRunStartRequest, +) (*corev1.DeliverRunStartResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + if req.GetStartRecord() == nil { + return nil, status.Error(codes.InvalidArgument, "start_record must not be nil") + } + handle, err := s.manager.Start() + if err != nil { + return nil, status.Errorf(codes.Internal, "start run session: %v", err) + } + return &corev1.DeliverRunStartResponse{ + Success: true, + Run: req.GetStartRecord(), + NewExperiment: true, + RunHandle: handle, + }, nil +} + +func (s *CoreService) UpsertColumns(context.Context, *corev1.UpsertColumnsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *CoreService) UpsertScalars(context.Context, *corev1.UpsertScalarsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *CoreService) UpsertMedia(context.Context, *corev1.UpsertMediaRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *CoreService) UpsertLogs(context.Context, *corev1.UpsertLogsRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +func (s *CoreService) UpsertSaves(context.Context, *corev1.UpsertSavesRequest) (*emptypb.Empty, error) { + return nil, s.rejectUpsert() +} + +// DeliverRunFinish 标记指定 run_handle 的会话结束。 +func (s *CoreService) DeliverRunFinish( + _ context.Context, + req *corev1.DeliverRunFinishRequest, +) (*corev1.DeliverRunFinishResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + if err := s.manager.Finish(req.GetRunHandle()); err != nil { + return nil, managerStatus(err) + } + return &corev1.DeliverRunFinishResponse{Success: true}, nil +} + +// GetOperationStats 查询指定会话的当前运行与排空状态。 +func (s *CoreService) GetOperationStats( + _ context.Context, + req *corev1.GetOperationStatsRequest, +) (*corev1.GetOperationStatsResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + runState, err := s.manager.State(req.GetRunHandle()) + if err != nil { + return nil, managerStatus(err) + } + state := operationv1.CoreState_CORE_STATE_RUNNING + if runState == manager.RunStateFinished { + state = operationv1.CoreState_CORE_STATE_FINISHED + } + return &corev1.GetOperationStatsResponse{ + Success: true, + Stats: &operationv1.OperationStats{State: state}, + }, nil +} + +// ConfirmRunFinish 确认会话已完成并释放相关资源。 +func (s *CoreService) ConfirmRunFinish( + _ context.Context, + req *corev1.ConfirmRunFinishRequest, +) (*corev1.ConfirmRunFinishResponse, error) { + if err := s.requireReady(); err != nil { + return nil, err + } + if err := s.manager.Confirm(req.GetRunHandle()); err != nil { + return nil, managerStatus(err) + } + return &corev1.ConfirmRunFinishResponse{Success: true}, nil +} + +// managerStatus 将 manager 层的领域错误转换为对应的 gRPC 状态码。 +func managerStatus(err error) error { + switch { + case errors.Is(err, manager.ErrEmptyHandle): + return status.Error(codes.InvalidArgument, err.Error()) + case errors.Is(err, manager.ErrRunNotFound): + return status.Error(codes.NotFound, err.Error()) + case errors.Is(err, manager.ErrRunNotFinished): + return status.Error(codes.FailedPrecondition, err.Error()) + default: + return status.Error(codes.Internal, err.Error()) + } +} diff --git a/core/internal/service/core_service_test.go b/core/internal/service/core_service_test.go new file mode 100644 index 000000000..ca0d3dee3 --- /dev/null +++ b/core/internal/service/core_service_test.go @@ -0,0 +1,203 @@ +package service + +import ( + "context" + "net" + "testing" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + + "github.com/swanhubx/swanlab/core/internal/manager" + "github.com/swanhubx/swanlab/core/internal/server" + corev1 "github.com/swanhubx/swanlab/core/proto/swanlab/grpc/core/v1" + operationv1 "github.com/swanhubx/swanlab/core/proto/swanlab/operation/v1" + runv1 "github.com/swanhubx/swanlab/core/proto/swanlab/run/v1" +) + +const ( + bufconnSize = 1 << 20 + callTimeout = 2 * time.Second + noShutdownWindow = 200 * time.Millisecond +) + +type testEnv struct { + client corev1.CoreServiceClient + ctrl *server.Controller +} + +func newTestEnv(t *testing.T) *testEnv { + t.Helper() + g := grpc.NewServer() + ctrl := server.NewController(g, 2*time.Second) + NewCoreService(ctrl, manager.New()).Register(g) + lis := bufconn.Listen(bufconnSize) + go func() { _ = g.Serve(lis) }() + conn, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return lis.DialContext(context.Background()) + }), + ) + if err != nil { + t.Fatalf("grpc.NewClient: %v", err) + } + t.Cleanup(func() { + _ = conn.Close() + ctrl.Shutdown("test-cleanup") + <-ctrl.Done() + }) + return &testEnv{client: corev1.NewCoreServiceClient(conn), ctrl: ctrl} +} + +func callCtx(t *testing.T) context.Context { + t.Helper() + ctx, cancel := context.WithTimeout(context.Background(), callTimeout) + t.Cleanup(cancel) + return ctx +} + +func spinup(t *testing.T, env *testEnv) { + t.Helper() + if _, err := env.client.SpinupService(callCtx(t), &corev1.SpinupServiceRequest{}); err != nil { + t.Fatalf("SpinupService: %v", err) + } +} + +func startRun(t *testing.T, env *testEnv) string { + t.Helper() + resp, err := env.client.DeliverRunStart(callCtx(t), &corev1.DeliverRunStartRequest{ + StartRecord: &runv1.StartRecord{}, + }) + if err != nil || !resp.GetSuccess() || resp.GetRunHandle() == "" { + t.Fatalf("DeliverRunStart: resp=%v err=%v", resp, err) + } + return resp.GetRunHandle() +} + +func TestSpinupAndTeardown(t *testing.T) { + env := newTestEnv(t) + spinup(t, env) + spinup(t, env) + if _, err := env.client.TeardownService(callCtx(t), &corev1.TeardownServiceRequest{}); err != nil { + t.Fatalf("TeardownService: %v", err) + } + select { + case <-env.ctrl.Done(): + case <-time.After(callTimeout): + t.Fatal("shutdown not completed after teardown") + } +} + +func TestRunRPCsRejectedBeforeReady(t *testing.T) { + env := newTestEnv(t) + ctx := callCtx(t) + for name, call := range map[string]func() error{ + "start": func() error { + _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{StartRecord: &runv1.StartRecord{}}) + return err + }, + "upsert": func() error { + _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: "any"}) + return err + }, + "stats": func() error { + _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "any"}) + return err + }, + "finish": func() error { + _, err := env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: "any"}) + return err + }, + "confirm": func() error { + _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: "any"}) + return err + }, + } { + if err := call(); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("%s before ready: err=%v, want FailedPrecondition", name, err) + } + } +} + +func TestUpsertsRemainUnimplemented(t *testing.T) { + env := newTestEnv(t) + spinup(t, env) + handle := startRun(t, env) + ctx := callCtx(t) + calls := []func() error{ + func() error { + _, err := env.client.UpsertColumns(ctx, &corev1.UpsertColumnsRequest{RunHandle: handle}) + return err + }, + func() error { + _, err := env.client.UpsertScalars(ctx, &corev1.UpsertScalarsRequest{RunHandle: handle}) + return err + }, + func() error { + _, err := env.client.UpsertMedia(ctx, &corev1.UpsertMediaRequest{RunHandle: handle}) + return err + }, + func() error { + _, err := env.client.UpsertLogs(ctx, &corev1.UpsertLogsRequest{RunHandle: handle}) + return err + }, + func() error { + _, err := env.client.UpsertSaves(ctx, &corev1.UpsertSavesRequest{RunHandle: handle}) + return err + }, + } + for i, call := range calls { + if err := call(); status.Code(err) != codes.Unimplemented { + t.Fatalf("upsert %d err=%v, want Unimplemented", i, err) + } + } +} + +func TestRunLifecycleAndErrorMapping(t *testing.T) { + env := newTestEnv(t) + spinup(t, env) + ctx := callCtx(t) + + if _, err := env.client.DeliverRunStart(ctx, &corev1.DeliverRunStartRequest{}); status.Code(err) != codes.InvalidArgument { + t.Fatalf("missing start_record err=%v, want InvalidArgument", err) + } + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{}); status.Code(err) != codes.InvalidArgument { + t.Fatalf("empty handle err=%v, want InvalidArgument", err) + } + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: "missing"}); status.Code(err) != codes.NotFound { + t.Fatalf("unknown handle err=%v, want NotFound", err) + } + + handle := startRun(t, env) + resp, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_RUNNING { + t.Fatalf("running stats: resp=%v err=%v", resp, err) + } + if _, err = env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("early confirm err=%v, want FailedPrecondition", err) + } + if _, err = env.client.DeliverRunFinish(ctx, &corev1.DeliverRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("DeliverRunFinish: %v", err) + } + resp, err = env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}) + if err != nil || resp.GetStats().GetState() != operationv1.CoreState_CORE_STATE_FINISHED { + t.Fatalf("finished stats: resp=%v err=%v", resp, err) + } + if _, err := env.client.ConfirmRunFinish(ctx, &corev1.ConfirmRunFinishRequest{RunHandle: handle}); err != nil { + t.Fatalf("ConfirmRunFinish: %v", err) + } + select { + case <-env.ctrl.Done(): + t.Fatal("ConfirmRunFinish must not stop the server") + case <-time.After(noShutdownWindow): + } + if _, err := env.client.GetOperationStats(ctx, &corev1.GetOperationStatsRequest{RunHandle: handle}); status.Code(err) != codes.NotFound { + t.Fatalf("released handle err=%v, want NotFound", err) + } +} From 5b01a6537df60c0666c304bd9238b8ed1a7e383f Mon Sep 17 00:00:00 2001 From: Nexisato <978452096@qq.com> Date: Thu, 24 Sep 2026 16:50:51 +0800 Subject: [PATCH 15/15] chore: update service mode name --- core/cmd/swanlab-core/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/cmd/swanlab-core/main.go b/core/cmd/swanlab-core/main.go index 87dce7ee8..7b22aa862 100644 --- a/core/cmd/swanlab-core/main.go +++ b/core/cmd/swanlab-core/main.go @@ -81,7 +81,7 @@ func run(args []string) int { parentPID := fs.Int("parent-pid", envInt(envParentPID), "expected parent PID; core exits when the parent exits, defaults to the actual parent at startup") detach := fs.Bool("detach", false, - "detached mode (reserved; accepted but ignored in this build)") + "detach from parent process (reserved; accepted but ignored in this build)") idleTimeout := fs.Duration("idle-timeout", 0, "detached idle timeout (reserved; accepted but ignored in this build)") if err := fs.Parse(args); err != nil { @@ -96,7 +96,7 @@ func run(args []string) int { return 0 } if *detach || *idleTimeout != 0 { - console.Warning("--detach/--idle-timeout accepted but ignored: detached mode is not implemented; running in owner mode") + console.Warning("--detach/--idle-timeout accepted but ignored: detached service is not implemented; core stays bound to the parent process") } if *listenAddr == "" && *portFilename == "" { console.Error("no listen endpoint: pass --listen (manual debug) or --port-filename (SDK startup convention)")