diff --git a/README.md b/README.md index c1f910f..f4aa291 100644 --- a/README.md +++ b/README.md @@ -255,10 +255,8 @@ func main() { if !errors.Is(err, fs.ErrNotExist) { log.Fatal(err) } - } else { - if _, err := coro.Context().Unmarshal(b); err != nil { - log.Fatal(err) - } + } else if err := coro.Context().Unmarshal(b); err != nil { + log.Fatal(err) } defer func() { @@ -308,7 +306,7 @@ More examples of how to use durable coroutines can be found in [examples](./exam #### Extend serialization -`coroutine` is able to seamlessly serialized and deserialize most types by +`coroutine` is able to seamlessly serialize and deserialize most types by default. However there are times when you may want to control the serialization of specific types. For example, `chan` values are not supported, or you may decide that some values need specific logic to be functional upon diff --git a/gen/proto/go/coroutine/v1/coroutine.pb.go b/gen/proto/go/coroutine/v1/coroutine.pb.go index 5046d97..d5d100e 100644 --- a/gen/proto/go/coroutine/v1/coroutine.pb.go +++ b/gen/proto/go/coroutine/v1/coroutine.pb.go @@ -37,6 +37,10 @@ type State struct { // Functions is the set of functions, methods and closures referenced // by the object graph. Functions []*Function `protobuf:"bytes,4,rep,name=functions,proto3" json:"functions,omitempty"` + // Regions are encoded regions of memory. + Regions []*Region `protobuf:"bytes,5,rep,name=regions,proto3" json:"regions,omitempty"` + // Root is the root object. + Root *Region `protobuf:"bytes,6,opt,name=root,proto3" json:"root,omitempty"` } func (x *State) Reset() { @@ -99,6 +103,20 @@ func (x *State) GetFunctions() []*Function { return nil } +func (x *State) GetRegions() []*Region { + if x != nil { + return x.Regions + } + return nil +} + +func (x *State) GetRoot() *Region { + if x != nil { + return x.Root + } + return nil +} + // Build is info about the build in which a durable coroutine // is/was running. type Build struct { @@ -164,6 +182,64 @@ func (x *Build) GetArch() string { return "" } +// Region is an encoded region of memory. +type Region struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Type is the type of the region. + Type int32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + // Data is the encoded contents of the memory region. + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Region) Reset() { + *x = Region{} + if protoimpl.UnsafeEnabled { + mi := &file_coroutine_v1_coroutine_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Region) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Region) ProtoMessage() {} + +func (x *Region) ProtoReflect() protoreflect.Message { + mi := &file_coroutine_v1_coroutine_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Region.ProtoReflect.Descriptor instead. +func (*Region) Descriptor() ([]byte, []int) { + return file_coroutine_v1_coroutine_proto_rawDescGZIP(), []int{2} +} + +func (x *Region) GetType() int32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *Region) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + var File_coroutine_v1_coroutine_proto protoreflect.FileDescriptor var file_coroutine_v1_coroutine_proto_rawDesc = []byte{ @@ -173,7 +249,7 @@ var file_coroutine_v1_coroutine_proto_rawDesc = []byte{ 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xa8, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x05, + 0x74, 0x6f, 0x22, 0x82, 0x02, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, @@ -183,24 +259,32 @@ var file_coroutine_v1_coroutine_proto_rawDesc = []byte{ 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3b, 0x0a, - 0x05, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x42, 0xbd, 0x01, 0x0a, 0x10, 0x63, - 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x42, - 0x0e, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, - 0x01, 0x5a, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x6f, - 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x67, 0x6f, 0x2f, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, - 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, - 0x58, 0xaa, 0x02, 0x0c, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x56, 0x31, - 0xca, 0x02, 0x0c, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0xe2, - 0x02, 0x18, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, - 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x43, 0x6f, 0x72, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x6e, 0x52, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, 0x0a, + 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x0a, + 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, + 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, + 0x6e, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x3b, 0x0a, 0x05, 0x42, 0x75, 0x69, 0x6c, 0x64, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, + 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x61, 0x72, 0x63, 0x68, 0x22, 0x30, 0x0a, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0xbd, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x63, + 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x43, 0x6f, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, + 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x63, + 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x72, 0x6f, + 0x75, 0x74, 0x69, 0x6e, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, 0x02, 0x0c, + 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0c, 0x43, + 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x18, 0x43, 0x6f, + 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0d, 0x43, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, + 0x6e, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -215,22 +299,25 @@ func file_coroutine_v1_coroutine_proto_rawDescGZIP() []byte { return file_coroutine_v1_coroutine_proto_rawDescData } -var file_coroutine_v1_coroutine_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_coroutine_v1_coroutine_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_coroutine_v1_coroutine_proto_goTypes = []interface{}{ (*State)(nil), // 0: coroutine.v1.State (*Build)(nil), // 1: coroutine.v1.Build - (*Type)(nil), // 2: coroutine.v1.Type - (*Function)(nil), // 3: coroutine.v1.Function + (*Region)(nil), // 2: coroutine.v1.Region + (*Type)(nil), // 3: coroutine.v1.Type + (*Function)(nil), // 4: coroutine.v1.Function } var file_coroutine_v1_coroutine_proto_depIdxs = []int32{ 1, // 0: coroutine.v1.State.build:type_name -> coroutine.v1.Build - 2, // 1: coroutine.v1.State.types:type_name -> coroutine.v1.Type - 3, // 2: coroutine.v1.State.functions:type_name -> coroutine.v1.Function - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 3, // 1: coroutine.v1.State.types:type_name -> coroutine.v1.Type + 4, // 2: coroutine.v1.State.functions:type_name -> coroutine.v1.Function + 2, // 3: coroutine.v1.State.regions:type_name -> coroutine.v1.Region + 2, // 4: coroutine.v1.State.root:type_name -> coroutine.v1.Region + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_coroutine_v1_coroutine_proto_init() } @@ -265,6 +352,18 @@ func file_coroutine_v1_coroutine_proto_init() { return nil } } + file_coroutine_v1_coroutine_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Region); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -272,7 +371,7 @@ func file_coroutine_v1_coroutine_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_coroutine_v1_coroutine_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/proto/go/coroutine/v1/coroutine_vtproto.pb.go b/gen/proto/go/coroutine/v1/coroutine_vtproto.pb.go index 985cb08..47556b1 100644 --- a/gen/proto/go/coroutine/v1/coroutine_vtproto.pb.go +++ b/gen/proto/go/coroutine/v1/coroutine_vtproto.pb.go @@ -47,6 +47,28 @@ func (m *State) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Root != nil { + size, err := m.Root.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if len(m.Regions) > 0 { + for iNdEx := len(m.Regions) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Regions[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + } if len(m.Functions) > 0 { for iNdEx := len(m.Functions) - 1; iNdEx >= 0; iNdEx-- { size, err := m.Functions[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) @@ -145,6 +167,51 @@ func (m *Build) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Region) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Region) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Region) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarint(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x12 + } + if m.Type != 0 { + i = encodeVarint(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *State) SizeVT() (n int) { if m == nil { return 0 @@ -171,6 +238,16 @@ func (m *State) SizeVT() (n int) { n += 1 + l + sov(uint64(l)) } } + if len(m.Regions) > 0 { + for _, e := range m.Regions { + l = e.SizeVT() + n += 1 + l + sov(uint64(l)) + } + } + if m.Root != nil { + l = m.Root.SizeVT() + n += 1 + l + sov(uint64(l)) + } n += len(m.unknownFields) return n } @@ -197,6 +274,23 @@ func (m *Build) SizeVT() (n int) { return n } +func (m *Region) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sov(uint64(m.Type)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *State) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -364,6 +458,76 @@ func (m *State) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Regions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Regions = append(m.Regions, &Region{}) + if err := m.Regions[len(m.Regions)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Root", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Root == nil { + m.Root = &Region{} + } + if err := m.Root.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -533,3 +697,107 @@ func (m *Build) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *Region) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Region: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Region: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/proto/coroutine/v1/coroutine.proto b/proto/coroutine/v1/coroutine.proto index d4d6cad..486fc18 100644 --- a/proto/coroutine/v1/coroutine.proto +++ b/proto/coroutine/v1/coroutine.proto @@ -21,6 +21,12 @@ message State { // Functions is the set of functions, methods and closures referenced // by the object graph. repeated Function functions = 4; + + // Regions are encoded regions of memory. + repeated Region regions = 5; + + // Root is the root object. + Region root = 6; } // Build is info about the build in which a durable coroutine @@ -30,3 +36,12 @@ message Build { string os = 2; string arch = 3; } + +// Region is an encoded region of memory. +message Region { + // Type is the type of the region. + int32 type = 1; + + // Data is the encoded contents of the memory region. + bytes data = 2; +} diff --git a/types/reflect.go b/types/reflect.go index 58ebde3..4818ee1 100644 --- a/types/reflect.go +++ b/types/reflect.go @@ -6,6 +6,8 @@ import ( "math" "reflect" "unsafe" + + coroutinev1 "github.com/stealthrocket/coroutine/gen/proto/go/coroutine/v1" ) func serializeType(s *Serializer, t reflect.Type) { @@ -355,48 +357,43 @@ func serializePointedAt(s *Serializer, t reflect.Type, p unsafe.Pointer) { return } - id, new := s.assignPointerID(p) - serializeVarint(s, int(id)) - if !new { - // This exact pointer has already been serialized. Write its ID - // and move on. - return - } - - // Now, this is pointer that is seen for the first time. - // Check the region of this pointer. r := s.containers.of(p) - // If this pointer does not belong to any region, write a negative - // offset to flag it is on its own, and write its data. + // If the pointer does not point to a known region encountered via + // scanning, create a new temporary region. This can occur when a + // custom serializer emits memory regions during serialization (and + // after the root object has been scanned). Note that we do not scan + // the memory region! This means it's not possible to alias this + // memory region (or other regions it points to that aren't known + // to the serializer). Scanning here might cause known regions to + // expand, invalidating those that have already been encoded. if !r.valid() { if t == nil { panic("cannot serialize unsafe.Pointer pointing to region of unknown size") } - serializeVarint(s, -1) - serializeAny(s, t, p) - return + r.addr = p + r.typ = t } - // The pointer points into a memory region. + id, new := s.assignPointerID(r.addr) + serializeVarint(s, int(id)) + offset := int(r.offset(p)) serializeVarint(s, offset) - // Write the type of the container. - serializeType(s, r.typ) - - // Serialize the parent. If offset is zero, we reuse the id to store the - // parent. We could have a more compact representation here, but right - // now we need this since the pointers <> id map in the serializer does - // not discriminate between the container and the first element of it. - if offset == 0 { - serializeVarint(s, int(id)) - serializeVarint(s, -1) - serializeAny(s, r.typ, r.addr) + if !new { return } - serializePointedAt(s, r.typ, r.addr) + + region := &coroutinev1.Region{ + Type: int32(s.types.ToType(r.typ)), + } + s.regions = append(s.regions, region) + + regionSer := s.fork() + serializeAny(regionSer, r.typ, r.addr) + region.Data = regionSer.b } func deserializePointedAt(d *Deserializer, t reflect.Type) reflect.Value { @@ -406,34 +403,38 @@ func deserializePointedAt(d *Deserializer, t reflect.Type) reflect.Value { // reflect.Value that contains a *T (where T is given by the argument // t). - ptr, id := d.readPtr() - if ptr != nil || id == 0 { // pointer already seen or nil - return reflect.NewAt(t, ptr) + id := deserializeVarint(d) + if id == 0 { + // Nil pointer. + return reflect.NewAt(t, unsafe.Pointer(nil)) } offset := deserializeVarint(d) - - // Negative offset means this is either a container or a standalone - // value. - if offset < 0 { - e := reflect.New(t) - ep := e.UnsafePointer() - d.store(id, ep) - deserializeAny(d, t, ep) - return e + if id == -1 { + // Pointer into static uint64 table. + p := staticPointer(offset) + return reflect.NewAt(t, p) } - // This pointer points into a container. Deserialize that one first, - // then return the pointer itself with an offset. - ct := deserializeType(d) + p := d.ptrs[sID(id)] + if p == nil { + // Deserialize the region. + if int(id) > len(d.regions) { + panic(fmt.Sprintf("region %d not found", id)) + } + region := d.regions[id-1] - // cp is a pointer to the container - cp := deserializePointedAt(d, ct) + regionType := d.types.ToReflect(typeid(region.Type)) + + regionDeser := d.fork(region.Data) + container := reflect.New(regionType) + p = container.UnsafePointer() + d.store(sID(id), p) + deserializeAny(regionDeser, regionType, p) + } // Create the pointer with an offset into the container. - ep := unsafe.Add(cp.UnsafePointer(), offset) - r := reflect.NewAt(t, ep) - return r + return reflect.NewAt(t, unsafe.Add(p, offset)) } func serializeMap(s *Serializer, t reflect.Type, p unsafe.Pointer) { @@ -451,13 +452,20 @@ func serializeMapReflect(s *Serializer, t reflect.Type, r reflect.Value) { id, new := s.assignPointerID(mapptr) serializeVarint(s, int(id)) + if !new { return } size := r.Len() - serializeVarint(s, size) + region := &coroutinev1.Region{ + Type: int32(s.types.ToType(t)), + } + s.regions = append(s.regions, region) + + regionSer := s.fork() + serializeVarint(regionSer, size) // TODO: allocs iter := r.MapRange() @@ -466,9 +474,11 @@ func serializeMapReflect(s *Serializer, t reflect.Type, r reflect.Value) { for iter.Next() { k.Set(iter.Key()) v.Set(iter.Value()) - serializeAny(s, t.Key(), k.Addr().UnsafePointer()) - serializeAny(s, t.Elem(), v.Addr().UnsafePointer()) + serializeAny(regionSer, t.Key(), k.Addr().UnsafePointer()) + serializeAny(regionSer, t.Elem(), v.Addr().UnsafePointer()) } + + region.Data = regionSer.b } func deserializeMap(d *Deserializer, t reflect.Type, p unsafe.Pointer) { @@ -477,30 +487,38 @@ func deserializeMap(d *Deserializer, t reflect.Type, p unsafe.Pointer) { } func deserializeMapReflect(d *Deserializer, t reflect.Type, r reflect.Value, p unsafe.Pointer) { - ptr, id := d.readPtr() + id := deserializeVarint(d) if id == 0 { - // nil map + r.SetZero() return } + ptr := d.ptrs[sID(id)] if ptr != nil { - // already deserialized at ptr existing := reflect.NewAt(t, ptr).Elem() r.Set(existing) return } - n := deserializeVarint(d) + if id > len(d.regions) { + panic(fmt.Sprintf("region %d not found", id)) + } + region := d.regions[id-1] + + regionDeser := d.fork(region.Data) + + n := deserializeVarint(regionDeser) if n < 0 { // nil map - return + panic("invalid map size") } + nv := reflect.MakeMapWithSize(t, n) r.Set(nv) - d.store(id, p) + d.store(sID(id), p) for i := 0; i < n; i++ { k := reflect.New(t.Key()) - deserializeAny(d, t.Key(), k.UnsafePointer()) + deserializeAny(regionDeser, t.Key(), k.UnsafePointer()) v := reflect.New(t.Elem()) - deserializeAny(d, t.Elem(), v.UnsafePointer()) + deserializeAny(regionDeser, t.Elem(), v.UnsafePointer()) r.SetMapIndex(k.Elem(), v.Elem()) } } @@ -522,8 +540,8 @@ func deserializeSlice(d *Deserializer, t reflect.Type, p unsafe.Pointer) { c := deserializeVarint(d) at := reflect.ArrayOf(c, t.Elem()) - ar := deserializePointedAt(d, at) + ar := deserializePointedAt(d, at) if ar.IsNil() { return } diff --git a/types/scan.go b/types/scan.go index 71b7d87..67e957a 100644 --- a/types/scan.go +++ b/types/scan.go @@ -238,7 +238,11 @@ func (c *containers) insert(x container) int { // It uses s.scanptrs to track which pointers it has already visited to avoid // infinite loops. It does not clean it up after. I'm sure there is something // more useful we could do with that. -func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { +func (s *Serializer) scan(t reflect.Type, p unsafe.Pointer) { + s.scan1(t, p, map[reflect.Value]struct{}{}) +} + +func (s *Serializer) scan1(t reflect.Type, p unsafe.Pointer, seen map[reflect.Value]struct{}) { if p == nil { return } @@ -250,10 +254,10 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { } r := reflect.NewAt(t, p) - if _, ok := s.scanptrs[r]; ok { + if _, ok := seen[r]; ok { return } - s.scanptrs[r] = struct{}{} + seen[r] = struct{}{} if r.IsNil() { return @@ -268,7 +272,7 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { es := int(et.Size()) for i := 0; i < t.Len(); i++ { ep := unsafe.Add(p, es*i) - scan(s, et, ep) + s.scan1(et, ep, seen) } case reflect.Slice: sr := r.Elem() @@ -288,7 +292,7 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { s.containers.add(xt, ep) for i := 0; i < sr.Len(); i++ { ep := unsafe.Add(ep, es*i) - scan(s, et, ep) + s.scan1(et, ep, seen) } case reflect.Interface: if r.Elem().IsNil() { @@ -308,7 +312,7 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { eptr = unsafe.Pointer(&xp) } - scan(s, et, eptr) + s.scan1(et, eptr, seen) case reflect.Struct: s.containers.add(t, p) n := t.NumField() @@ -316,14 +320,14 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { f := t.Field(i) ft := f.Type fp := unsafe.Add(p, f.Offset) - scan(s, ft, fp) + s.scan1(ft, fp, seen) } case reflect.Pointer: if r.Elem().IsNil() { return } ep := r.Elem().UnsafePointer() - scan(s, t.Elem(), ep) + s.scan1(t.Elem(), ep, seen) case reflect.String: str := *(*string)(p) sp := unsafe.StringData(str) @@ -344,7 +348,7 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { for iter.Next() { k := iter.Key() kp := (*iface)(unsafe.Pointer(&k)).ptr - scan(s, kt, kp) + s.scan1(kt, kp, seen) v := iter.Value() vp := (*iface)(unsafe.Pointer(&v)).ptr @@ -353,7 +357,7 @@ func scan(s *Serializer, t reflect.Type, p unsafe.Pointer) { vp = unsafe.Pointer(&xp) } - scan(s, vt, vp) + s.scan1(vt, vp, seen) } case reflect.Bool, reflect.Int, diff --git a/types/serde.go b/types/serde.go index e3adfdb..8367874 100644 --- a/types/serde.go +++ b/types/serde.go @@ -46,17 +46,23 @@ func Serialize(x any) ([]byte, error) { p := wr.UnsafePointer() // *interface{} t := wr.Elem().Type() // what x contains - scan(s, t, p) - // scan dirties s.scanptrs, so clean it up. - clear(s.scanptrs) + // Scan pointers to collect memory regions. + s.scan(t, p) + + rootType := reflect.TypeOf(wr.Elem().Interface()) + rootTypeID := s.types.ToType(rootType) serializeAny(s, t, p) state := &coroutinev1.State{ - State: s.b, Build: buildInfo, Types: s.types.types, Functions: s.funcs.funcs, + Regions: s.regions, + Root: &coroutinev1.Region{ + Type: int32(rootTypeID), + Data: s.b, + }, } return state.MarshalVT() } @@ -71,12 +77,14 @@ func Deserialize(b []byte) (interface{}, error) { return nil, fmt.Errorf("%w: got %v, expect %v", ErrBuildIDMismatch, state.Build.Id, buildInfo.Id) } - d := newDeserializer(state.State, state.Types, state.Functions) + d := newDeserializer(state.Root.Data, state.Types, state.Functions, state.Regions) + var x interface{} px := &x t := reflect.TypeOf(px).Elem() p := unsafe.Pointer(px) deserializeInterface(d, t, p) + if len(d.b) != 0 { return nil, errors.New("trailing bytes") } @@ -84,44 +92,39 @@ func Deserialize(b []byte) (interface{}, error) { } type Deserializer struct { - serdes *serdemap - types *typemap - funcs *funcmap - - // TODO: make it a slice since pointer ids is the sequence of integers - // starting at 1. - ptrs map[sID]unsafe.Pointer + *deserializerContext // input b []byte } -func newDeserializer(b []byte, ctypes []*coroutinev1.Type, cfuncs []*coroutinev1.Function) *Deserializer { +type deserializerContext struct { + serdes *serdemap + types *typemap + funcs *funcmap + regions []*coroutinev1.Region + ptrs map[sID]unsafe.Pointer +} + +func newDeserializer(b []byte, ctypes []*coroutinev1.Type, cfuncs []*coroutinev1.Function, regions []*coroutinev1.Region) *Deserializer { types := newTypeMap(serdes, ctypes) return &Deserializer{ - serdes: serdes, - types: types, - funcs: newFuncMap(types, cfuncs), - ptrs: make(map[sID]unsafe.Pointer), - b: b, + &deserializerContext{ + serdes: serdes, + types: types, + funcs: newFuncMap(types, cfuncs), + regions: regions, + ptrs: make(map[sID]unsafe.Pointer), + }, + b, } } -func (d *Deserializer) readPtr() (unsafe.Pointer, sID) { - x, n := binary.Varint(d.b) - d.b = d.b[n:] - - // pointer into static uint64 table - if x == -1 { - x, n = binary.Varint(d.b) - d.b = d.b[n:] - p := staticPointer(int(x)) - return p, 0 +func (d *Deserializer) fork(b []byte) *Deserializer { + return &Deserializer{ + d.deserializerContext, + b, } - - i := sID(x) - p := d.ptrs[i] - return p, i } func (d *Deserializer) store(i sID, p unsafe.Pointer) { @@ -158,29 +161,39 @@ func (d *Deserializer) store(i sID, p unsafe.Pointer) { // shared memory. Only outermost containers are serialized. All pointers either // point to a container, or an offset into that container. type Serializer struct { + *serializerContext + + // Output + b []byte +} + +type serializerContext struct { serdes *serdemap types *typemap funcs *funcmap ptrs map[unsafe.Pointer]sID + regions []*coroutinev1.Region containers containers - - // TODO: move out. just used temporarily by scan - scanptrs map[reflect.Value]struct{} - - // Output - b []byte } func newSerializer() *Serializer { types := newTypeMap(serdes, nil) return &Serializer{ - serdes: serdes, - types: types, - funcs: newFuncMap(types, nil), - ptrs: make(map[unsafe.Pointer]sID), - scanptrs: make(map[reflect.Value]struct{}), - b: make([]byte, 0, 128), + &serializerContext{ + serdes: serdes, + types: types, + funcs: newFuncMap(types, nil), + ptrs: make(map[unsafe.Pointer]sID), + }, + make([]byte, 0, 128), + } +} + +func (s *Serializer) fork() *Serializer { + return &Serializer{ + s.serializerContext, + make([]byte, 0, 128), } }