From 0d57cc1d3611bda070f5c3cf03d678b4b97a3494 Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 14:24:39 +0300 Subject: [PATCH 1/8] fix: no kvget on cache miss when GetValue is called --- statefun/cache/cache.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/statefun/cache/cache.go b/statefun/cache/cache.go index bee767f9..74cc467e 100644 --- a/statefun/cache/cache.go +++ b/statefun/cache/cache.go @@ -622,11 +622,11 @@ func (cs *Store) GetValue(key string) ([]byte, error) { var result []byte = nil var resultError error = nil - cacheMiss := true + //cacheMiss := true if keyLastToken, parentCacheStoreValue := cs.getLastKeyTokenAndItsParentCacheStoreValue(key, false); len(keyLastToken) > 0 && parentCacheStoreValue != nil { if csv, ok := parentCacheStoreValue.LoadChild(keyLastToken); ok { - cacheMiss = false // Value exists in cache - no cache miss then + //cacheMiss = false // Value exists in cache - no cache miss then csv.RLock("GetValue") if !csv.ValueExists() { // Value was intentionally deleted and was marked so, no cache miss policy can be applied here resultError = fmt.Errorf("value for key=%s does not exist", key) @@ -648,7 +648,11 @@ func (cs *Store) GetValue(key string) ([]byte, error) { } // Cache miss ----------------------------------------- - if cacheMiss { + if result == nil { + resultError = fmt.Errorf("Value for for key=%s does not exist", key) + } + + /*if cacheMiss { if entry, err := customNatsKv.KVGet(cs.js, cs.kv, cs.toStoreKey(key)); err == nil { key := cs.fromStoreKey(entry.Key()) valueBytes := entry.Value() @@ -664,10 +668,10 @@ func (cs *Store) GetValue(key string) ([]byte, error) { if json, ok := easyjson.JSONFromBytes(result); ok { cs.SetValueJSON(key, &json, false, kvRecordTime, "") lg.Logf(lg.WarnLevel, "Value for key=%s is JSON in KV, use GetValueJSON method", key) - resultError = nil + resultError = nil } else { resultError = fmt.Errorf("failed to parse JSON for key=%s", key) - } + } default: resultError = fmt.Errorf("unknown flag %d for key=%s", appendFlag, key) } @@ -677,9 +681,9 @@ func (cs *Store) GetValue(key string) ([]byte, error) { } else { resultError = err } - } - + }*/ // ---------------------------------------------------- + return result, resultError } From 0ccbb9c3c64e7486ceae843cc0e374aab8166ebf Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 14:24:49 +0300 Subject: [PATCH 2/8] update: in link creation and deletion are optimised --- embedded/graph/crud/ll_crud.go | 42 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/embedded/graph/crud/ll_crud.go b/embedded/graph/crud/ll_crud.go index 275c324b..a4634efc 100644 --- a/embedded/graph/crud/ll_crud.go +++ b/embedded/graph/crud/ll_crud.go @@ -579,16 +579,20 @@ func LLAPILinkCreate(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex } // Create in link on descendant vertex -------------------- - nextCallPayload := easyjson.NewJSONObject() - nextCallPayload.SetByPath("in_name", easyjson.NewJSON(linkName)) - nextCallPayload.SetByPath("in_type", easyjson.NewJSON(linkType)) - nextCallPayload.SetByPath("op_time", easyjson.NewJSON(opTime)) + if ctx.Domain.GetDomainFromObjectID(toId) == ctx.Domain.Name() { + ctx.Domain.Cache().SetValue(fmt.Sprintf(InLinkKeyPrefPattern+KeySuff2Pattern, toId, selfID, linkName), []byte(linkType), true, opTime, "") + } else { + nextCallPayload := easyjson.NewJSONObject() + nextCallPayload.SetByPath("in_name", easyjson.NewJSON(linkName)) + nextCallPayload.SetByPath("in_type", easyjson.NewJSON(linkType)) + nextCallPayload.SetByPath("op_time", easyjson.NewJSON(opTime)) - om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, ctx.Self.Typename, makeSequenceFreeParentBasedID(ctx, toId, "inlink"), injectParentHoldsLocks(ctx, &nextCallPayload), ctx.Options))) - if om.GetLastSyncOp().Status == sfMediators.SYNC_OP_STATUS_FAILED { - operationKeysMutexUnlock(ctx) - system.MsgOnErrorReturn(om.ReplyWithData(resultWithOpStack(nil, opStack).GetPtr())) - return + om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, ctx.Self.Typename, makeSequenceFreeParentBasedID(ctx, toId, "inlink"), injectParentHoldsLocks(ctx, &nextCallPayload), ctx.Options))) + if om.GetLastSyncOp().Status == sfMediators.SYNC_OP_STATUS_FAILED { + operationKeysMutexUnlock(ctx) + system.MsgOnErrorReturn(om.ReplyWithData(resultWithOpStack(nil, opStack).GetPtr())) + return + } } // -------------------------------------------------------- @@ -844,15 +848,19 @@ func LLAPILinkDelete(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex addLinkOpToOpStack(opStack, ctx.Self.Typename, selfID, toId, linkName, linkType, oldLinkBody, nil) // Delete in link on descendant vertex -------------------- - nextCallPayload := easyjson.NewJSONObject() - nextCallPayload.SetByPath("in_name", easyjson.NewJSON(linkName)) - nextCallPayload.SetByPath("op_time", easyjson.NewJSON(opTime)) + if ctx.Domain.GetDomainFromObjectID(toId) == ctx.Domain.Name() { + ctx.Domain.Cache().DeleteValue(fmt.Sprintf(InLinkKeyPrefPattern+KeySuff2Pattern, toId, selfID, linkName), true, opTime, "") + } else { + nextCallPayload := easyjson.NewJSONObject() + nextCallPayload.SetByPath("in_name", easyjson.NewJSON(linkName)) + nextCallPayload.SetByPath("op_time", easyjson.NewJSON(opTime)) - om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, ctx.Self.Typename, makeSequenceFreeParentBasedID(ctx, toId, "inlink"), injectParentHoldsLocks(ctx, &nextCallPayload), ctx.Options))) - if om.GetLastSyncOp().Status == sfMediators.SYNC_OP_STATUS_FAILED { - operationKeysMutexUnlock(ctx) - system.MsgOnErrorReturn(om.ReplyWithData(resultWithOpStack(nil, opStack).GetPtr())) - return + om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, ctx.Self.Typename, makeSequenceFreeParentBasedID(ctx, toId, "inlink"), injectParentHoldsLocks(ctx, &nextCallPayload), ctx.Options))) + if om.GetLastSyncOp().Status == sfMediators.SYNC_OP_STATUS_FAILED { + operationKeysMutexUnlock(ctx) + system.MsgOnErrorReturn(om.ReplyWithData(resultWithOpStack(nil, opStack).GetPtr())) + return + } } // -------------------------------------------------------- From 0912c70ba760e74c0bd9dfb88e4b0ba8e4b86b3b Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 14:25:05 +0300 Subject: [PATCH 3/8] update: cache for object types and object link type between two types --- embedded/graph/crud/hl_crud.go | 41 ++++++++++--- embedded/graph/crud/hl_crud_helpers.go | 79 +++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 10 deletions(-) diff --git a/embedded/graph/crud/hl_crud.go b/embedded/graph/crud/hl_crud.go index 4105e5e6..4e798c8e 100644 --- a/embedded/graph/crud/hl_crud.go +++ b/embedded/graph/crud/hl_crud.go @@ -155,6 +155,8 @@ func DeleteType(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextProc om.AggregateOpMsg(m) + cachePurgeTypeEdgesForType(selfID) + PolyTypeGoalFinalize(ctx, polyTypeData) om.Reply() @@ -287,6 +289,10 @@ func CreateObject(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextPr executeTriggersFromLLOpStack(ctx, opStack, "", "") } + if om.GetStatus() == sfMediators.SYNC_OP_STATUS_OK { + cacheSetObjectType(selfID, originType) + } + replyWithoutOpStack(om, ctx, targetReply) } @@ -369,6 +375,7 @@ func DeleteObject(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextPr executeTriggersFromLLOpStack(ctx, j, selfID, objectType) } + cacheDeleteObjectType(selfID) replyWithoutOpStack(om, ctx) } @@ -497,6 +504,10 @@ func CreateTypesLink(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, "functions.graph.api.link.create", makeSequenceFreeParentBasedID(ctx, selfID), injectParentHoldsLocks(ctx, &link), ctx.Options))) operationKeysMutexUnlock(ctx) + if om.GetStatus() == sfMediators.SYNC_OP_STATUS_OK { + cacheSetTypeEdge(selfID, toType, objectLinkType) + } + om.Reply() } @@ -545,6 +556,14 @@ func UpdateTypesLink(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, "functions.graph.api.link.update", makeSequenceFreeParentBasedID(ctx, selfID), injectParentHoldsLocks(ctx, &link), ctx.Options))) operationKeysMutexUnlock(ctx) + if om.GetStatus() == sfMediators.SYNC_OP_STATUS_OK { + if ctx.Payload.PathExists("body.type") { + if newLT, ok := ctx.Payload.GetByPath("body.type").AsString(); ok && newLT != "" { + cacheSetTypeEdge(selfID, toType, newLT) + } + } + } + om.Reply() } @@ -613,6 +632,8 @@ func DeleteTypesLink(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, "functions.graph.api.link.delete", makeSequenceFreeParentBasedID(ctx, selfID), injectParentHoldsLocks(ctx, &objectLink), ctx.Options))) operationKeysMutexUnlock(ctx) + cacheDeleteTypeEdge(selfID, toType) + PolyTypeGoalFinalize(ctx, polyTypeData) for _, lateTrigger := range lateTriggersArr { @@ -738,17 +759,8 @@ func UpdateObjectsLink(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunCont } objectToID = ctx.Domain.CreateObjectIDWithThisDomain(objectToID, false) - operationKeysMutexLock(ctx, []string{selfID, objectToID}, true) - _, _, linkType, err := getReferenceLinkTypeBetweenTwoObjects(ctx, selfID, objectToID) - if err != nil { - operationKeysMutexUnlock(ctx) - om.AggregateOpMsg(sfMediators.OpMsgFailed(err.Error())).Reply() - return - } - objectLink := easyjson.NewJSONObject() objectLink.SetByPath("to", easyjson.NewJSON(objectToID)) - objectLink.SetByPath("type", easyjson.NewJSON(linkType)) objectLink.SetByPath("body", ctx.Payload.GetByPath("body")) if ctx.Payload.PathExists("tags") { objectLink.SetByPath("tags", ctx.Payload.GetByPath("tags")) @@ -766,6 +778,17 @@ func UpdateObjectsLink(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunCont options := ctx.Options.Clone() options.SetByPath("op_stack", easyjson.NewJSON(true)) + + operationKeysMutexLock(ctx, []string{selfID, objectToID}, true) + _, _, linkType, err := getReferenceLinkTypeBetweenTwoObjects(ctx, selfID, objectToID) + if err != nil { + operationKeysMutexUnlock(ctx) + om.AggregateOpMsg(sfMediators.OpMsgFailed(err.Error())).Reply() + return + } + + objectLink.SetByPath("type", easyjson.NewJSON(linkType)) + om.AggregateOpMsg(sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, "functions.graph.api.link.update", makeSequenceFreeParentBasedID(ctx, selfID), injectParentHoldsLocks(ctx, &objectLink), &options))) operationKeysMutexUnlock(ctx) diff --git a/embedded/graph/crud/hl_crud_helpers.go b/embedded/graph/crud/hl_crud_helpers.go index 3efd3d67..2e46e8c5 100644 --- a/embedded/graph/crud/hl_crud_helpers.go +++ b/embedded/graph/crud/hl_crud_helpers.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "sync" "github.com/foliagecp/easyjson" "github.com/foliagecp/sdk/statefun" @@ -27,6 +28,69 @@ const ( BUILT_IN_OBJECT_NAV = "nav" ) +// ---------------------------- +var ( + // key: objectID -> typeID + objectTypeCache sync.Map + // key: fromType -> *sync.Map(toType -> objectLinkType) + type2TypeObjectLinkTypeCache sync.Map +) + +func cacheGetObjectType(objectID string) (string, bool) { + if v, ok := objectTypeCache.Load(objectID); ok { + if s, ok := v.(string); ok && s != "" { + return s, true + } + } + return "", false +} +func cacheSetObjectType(objectID, typeID string) { objectTypeCache.Store(objectID, typeID) } +func cacheDeleteObjectType(objectID string) { objectTypeCache.Delete(objectID) } + +func cacheGetTypeEdge(fromType, toType string) (string, bool) { + if v, ok := type2TypeObjectLinkTypeCache.Load(fromType); ok { + if m, ok := v.(*sync.Map); ok { + if lt, ok := m.Load(toType); ok { + if s, ok := lt.(string); ok && s != "" { + return s, true + } + } + } + } + return "", false +} +func cacheSetTypeEdge(fromType, toType, linkType string) { + var m *sync.Map + if v, ok := type2TypeObjectLinkTypeCache.Load(fromType); ok { + m, _ = v.(*sync.Map) + } + if m == nil { + m = &sync.Map{} + type2TypeObjectLinkTypeCache.Store(fromType, m) + } + m.Store(toType, linkType) +} +func cacheDeleteTypeEdge(fromType, toType string) { + if v, ok := type2TypeObjectLinkTypeCache.Load(fromType); ok { + if m, ok := v.(*sync.Map); ok { + m.Delete(toType) + } + } +} +func cachePurgeTypeEdgesForType(typeID string) { + // outcome + type2TypeObjectLinkTypeCache.Delete(typeID) + // income + type2TypeObjectLinkTypeCache.Range(func(k, v any) bool { + if m, ok := v.(*sync.Map); ok { + m.Delete(typeID) + } + return true + }) +} + +// ----------------------------------------------------------------------------- + func typeOperationRedirectedToHub(ctx *sfPlugins.StatefunContextProcessor) bool { if ctx.Domain.Name() != ctx.Domain.HubDomainName() { om := sfMediators.NewOpMediator(ctx) @@ -111,6 +175,10 @@ func getTypeTriggers(ctx *sfPlugins.StatefunContextProcessor, typeName string) * } func findObjectType(ctx *sfPlugins.StatefunContextProcessor, objectID string) (string, error) { + if t, ok := cacheGetObjectType(objectID); ok { + return t, nil + } + options := easyjson.NewJSONObject() if ctx.Options != nil { options = ctx.Options.Clone() @@ -120,8 +188,11 @@ func findObjectType(ctx *sfPlugins.StatefunContextProcessor, objectID string) (s som := sfMediators.OpMsgFromSfReply(ctx.Request(sfPlugins.AutoRequestSelect, "functions.cmdb.api.object.read", makeSequenceFreeParentBasedID(ctx, id), injectParentHoldsLocks(ctx, nil), &options)) if som.Status == sfMediators.SYNC_OP_STATUS_OK { - return som.Data.GetByPath("type").AsStringDefault(""), nil + tp := som.Data.GetByPath("type").AsStringDefault("") + cacheSetObjectType(objectID, tp) + return tp, nil } + return "", fmt.Errorf(som.Details) } @@ -174,6 +245,10 @@ func getReferenceLinkTypeBetweenTwoObjects(ctx *sfPlugins.StatefunContextProcess } func getObjectsLinkTypeFromTypesLink(ctx *sfPlugins.StatefunContextProcessor, fromType, toType string) (string, error) { + if lt, ok := cacheGetTypeEdge(fromType, toType); ok { + return lt, nil + } + linkBody, err := getLinkBody(ctx, fromType, toType) if err != nil { return "", err @@ -183,6 +258,8 @@ func getObjectsLinkTypeFromTypesLink(ctx *sfPlugins.StatefunContextProcessor, fr if !ok { return "", fmt.Errorf("type of a link was not defined in link type") } + + cacheSetTypeEdge(fromType, toType, linkType) return linkType, nil } From f53c3ed753c283de371857d70a49e7de80581724 Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 22:11:47 +0300 Subject: [PATCH 4/8] fix: cache miss for GetValueJSON --- statefun/cache/cache.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/statefun/cache/cache.go b/statefun/cache/cache.go index 74cc467e..0b485ffb 100644 --- a/statefun/cache/cache.go +++ b/statefun/cache/cache.go @@ -718,7 +718,11 @@ func (cs *Store) GetValueJSON(key string) (*easyjson.JSON, error) { } // ---------------------Cache miss-------------------------- - if entry, err := customNatsKv.KVGet(cs.js, cs.kv, cs.toStoreKey(key)); err == nil { + if result == nil { + resultError = fmt.Errorf("Value for for key=%s does not exist", key) + } + + /*if entry, err := customNatsKv.KVGet(cs.js, cs.kv, cs.toStoreKey(key)); err == nil { key := cs.fromStoreKey(entry.Key()) valueBytes := entry.Value() @@ -757,7 +761,7 @@ func (cs *Store) GetValueJSON(key string) (*easyjson.JSON, error) { } } else { resultError = err - } + }*/ return result, resultError } From 1e72af851dec5cdad5a29bf68d2b9b598b0303de Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 22:38:33 +0300 Subject: [PATCH 5/8] update: lru size increased for large models --- statefun/cache/cache_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statefun/cache/cache_config.go b/statefun/cache/cache_config.go index 6a1c7652..f8812d19 100644 --- a/statefun/cache/cache_config.go +++ b/statefun/cache/cache_config.go @@ -2,7 +2,7 @@ package cache const ( KVStorePrefix = "store" - LRUSize = 1000000 + LRUSize = 100000000 LevelSubscriptionNotificationsBufferMaxSize = 30000 // ~16Mb: elemenets := 16 * 1024 * 1024 / (64 + 512), where 512 - avg value size, 64 - avg key size LazyWriterValueProcessDelayMkS = 500 LazyWriterRepeatDelayMkS = 100000 From 3a29603a85932e964a95577329608e87c2838539 Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 22:56:23 +0300 Subject: [PATCH 6/8] fix: LLAPIVertexRead removed double vertex body read; no read lock when no details --- embedded/graph/crud/ll_crud.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/embedded/graph/crud/ll_crud.go b/embedded/graph/crud/ll_crud.go index a4634efc..9ca653a3 100644 --- a/embedded/graph/crud/ll_crud.go +++ b/embedded/graph/crud/ll_crud.go @@ -379,24 +379,29 @@ Reply: op_stack: json array - optional */ func LLAPIVertexRead(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextProcessor) { + details := ctx.Payload.GetByPath("details").AsBoolDefault(false) + selfID := getOriginalID(ctx.Self.ID) om := sfMediators.NewOpMediator(ctx) - operationKeysMutexLock(ctx, []string{selfID}, false) - _, err := ctx.Domain.Cache().GetValueJSON(selfID) + if details { + operationKeysMutexLock(ctx, []string{selfID}, false) + } + j, err := ctx.Domain.Cache().GetValueJSON(selfID) if err != nil { // If vertex does not exist - operationKeysMutexUnlock(ctx) + if details { + operationKeysMutexUnlock(ctx) + } om.AggregateOpMsg(sfMediators.OpMsgIdle(fmt.Sprintf("vertex with id=%s does not exist", selfID))).Reply() return } opStack := getOpStackFromOptions(ctx.Options) - j := getVertexBody(ctx, selfID) result := easyjson.NewJSONObjectWithKeyValue("body", *j) - if ctx.Payload.GetByPath("details").AsBoolDefault(false) { + if details { outLinkNames := []string{} outLinkTypes := []string{} outLinkIds := []string{} @@ -448,8 +453,6 @@ func LLAPIVertexRead(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContex result.SetByPath("links.out.ids", easyjson.NewJSON(outLinkIds)) result.SetByPath("links.in", inLinks) - } else { - operationKeysMutexUnlock(ctx) } addVertexOpToOpStack(opStack, ctx.Self.Typename, selfID, nil, nil) From 33317d85dbd631a7cc2e88bff461992aa119c870 Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Thu, 6 Nov 2025 23:17:07 +0300 Subject: [PATCH 7/8] fix: LLAPILinkRead no mutex lock when no details --- embedded/graph/crud/ll_crud.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/embedded/graph/crud/ll_crud.go b/embedded/graph/crud/ll_crud.go index 9ca653a3..7d453639 100644 --- a/embedded/graph/crud/ll_crud.go +++ b/embedded/graph/crud/ll_crud.go @@ -909,32 +909,34 @@ func LLAPILinkRead(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextP opStack := getOpStackFromOptions(ctx.Options) - //operationKeysMutexLock(ctx, []string{selfID}, true) linkType, linkName, toId, linkExists := getFullLinkInfoFromSpecifiedIdentifier(ctx) if !linkExists { - //operationKeysMutexUnlock(ctx) om.AggregateOpMsg(sfMediators.OpMsgIdle(fmt.Sprintf("link from=%s with name=%s does not exist", ctx.Self.ID, linkName))).Reply() return } if !validLinkName.MatchString(linkName) { - //operationKeysMutexUnlock(ctx) om.AggregateOpMsg(sfMediators.OpMsgFailed("invalid link name")).Reply() return } - //operationKeysMutexUnlock(ctx) - operationKeysMutexLock(ctx, []string{selfID, toId}, false) + details := ctx.Payload.GetByPath("details").AsBoolDefault(false) + + if details { + operationKeysMutexLock(ctx, []string{selfID, toId}, false) + } linkBody, err := ctx.Domain.Cache().GetValueJSON(fmt.Sprintf(OutLinkBodyKeyPrefPattern+KeySuff1Pattern, selfID, linkName)) if err != nil { - operationKeysMutexUnlock(ctx) + if details { + operationKeysMutexUnlock(ctx) + } om.AggregateOpMsg(sfMediators.OpMsgFailed(fmt.Sprintf("link body from=%s with name=%s does not exist", selfID, linkName))).Reply() return } result := easyjson.NewJSONObjectWithKeyValue("body", *linkBody) - if ctx.Payload.GetByPath("details").AsBoolDefault(false) { + if details { tags := []string{} tagKeys := ctx.Domain.Cache().GetKeysByPattern(fmt.Sprintf(OutLinkIndexPrefPattern+KeySuff3Pattern, selfID, linkName, "tag", ">")) operationKeysMutexUnlock(ctx) @@ -950,8 +952,6 @@ func LLAPILinkRead(_ sfPlugins.StatefunExecutor, ctx *sfPlugins.StatefunContextP result.SetByPath("to", easyjson.NewJSON(toId)) result.SetByPath("tags", easyjson.NewJSON(tags)) - } else { - operationKeysMutexUnlock(ctx) } addLinkOpToOpStack(opStack, ctx.Self.Typename, selfID, toId, linkName, linkType, nil, nil) From 38e9347a03c4813663d85bda555ff0c9b37f6a51 Mon Sep 17 00:00:00 2001 From: Andrei Shipov Date: Fri, 7 Nov 2025 02:27:10 +0300 Subject: [PATCH 8/8] update: export FindObjectType --- embedded/graph/crud/hl_crud_helpers.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/embedded/graph/crud/hl_crud_helpers.go b/embedded/graph/crud/hl_crud_helpers.go index 2e46e8c5..6fdd5322 100644 --- a/embedded/graph/crud/hl_crud_helpers.go +++ b/embedded/graph/crud/hl_crud_helpers.go @@ -174,6 +174,10 @@ func getTypeTriggers(ctx *sfPlugins.StatefunContextProcessor, typeName string) * return easyjson.NewJSONObject().GetPtr() } +func FindObjectType(ctx *sfPlugins.StatefunContextProcessor, objectID string) (string, error) { + return findObjectType(ctx, objectID) +} + func findObjectType(ctx *sfPlugins.StatefunContextProcessor, objectID string) (string, error) { if t, ok := cacheGetObjectType(objectID); ok { return t, nil