{"record":{"id":"4f845d89db0fc708","repo":"siyuan-note/siyuan","slug":"mobile-export-lease-s-already-exists","errorCode":null,"errorMessage":"mobile export lease [%s] already exists","messagePattern":"mobile export lease \\[(.+?)\\] already exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/encrypted_export.go","lineNumber":378,"sourceCode":"}\n\nfunc registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {\n\tinfo, err := os.Stat(artifact)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif info.IsDir() {\n\t\treturn nil, fmt.Errorf(\"export artifact [%s] is a directory\", artifact)\n\t}\n\tstate := &mobileExportLeaseState{\n\t\tboxID:      boxID,\n\t\tcleanupDir: cleanupDir,\n\t\texpiresAt:  time.Now().Add(mobileExportLeaseTTL),\n\t}\n\tmobileExportLeases.Lock()\n\tif _, exists := mobileExportLeases.leases[leaseID]; exists {\n\t\tmobileExportLeases.Unlock()\n\t\treturn nil, fmt.Errorf(\"mobile export lease [%s] already exists\", leaseID)\n\t}\n\tmobileExportLeases.leases[leaseID] = state\n\tstate.timer = time.AfterFunc(mobileExportLeaseTTL, func() {\n\t\treleaseMobileExportLease(leaseID, true)\n\t})\n\tmobileExportLeases.Unlock()\n\treturn &MobileExportLease{ID: leaseID, Path: artifact, Name: name, Size: info.Size()}, nil\n}\n\n// ReleaseExportArtifactLease 释放导出产物租约；重复调用不会产生副作用。\nfunc ReleaseExportArtifactLease(leaseID string) {\n\treleaseMobileExportLease(leaseID, false)\n}\n\n// ReleaseMobileExportLease 释放移动端导出租约；重复调用不会产生副作用。\nfunc ReleaseMobileExportLease(leaseID string) {\n\tReleaseExportArtifactLease(leaseID)\n}","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/encrypted_export.go#L360-L396","documentation":"Mobile export leases are tracked in an in-process map keyed by leaseID. registerMobileExportLeaseWithID inserts under a mutex and refuses to overwrite an existing lease ID, returning this error when the ID is already live. This prevents one holder's release/TTL cleanup from destroying another holder's lease state.","triggerScenarios":"Calling registerMobileExportLease / AcquireExportArtifactLease twice with the same explicit lease ID without the first lease being released (explicitly or via the mobileExportLeaseTTL timer); a client reusing a cached leaseID after the first request succeeded.","commonSituations":"A mobile client retries an export request with a deterministic ID before the previous lease expired; duplicate concurrent requests carrying the same client-generated ID; tests that forget to release leases between cases.","solutions":["Generate a fresh unique lease ID (random/UUID) per request instead of reusing an existing one.","Release the prior lease first (call the release API) or wait for the TTL expiry, then re-acquire.","Treat this as a benign conflict: catch it and fetch/reuse the already-existing lease rather than re-registering."],"exampleFix":"// before\nlease, err := model.AcquireMobileExportLeaseWithID(\"lease-1\", boxID, artifact)\n// after\nleaseID := uuid.NewString()\nlease, err := model.AcquireMobileExportLeaseWithID(leaseID, boxID, artifact)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"lease, err := registerMobileExportLeaseWithID(id, boxID, artifact, name, dir)\nif err != nil && strings.Contains(err.Error(), \"already exists\") {\n    lease = getExistingLease(id) // reuse instead of re-registering\n}","preventionTips":["Generate unique lease IDs (UUID) per request","Release leases promptly; do not wait only for TTL expiry","Make retries idempotent by fetching the existing lease on conflict"],"tags":["concurrency","export","lease"],"backgroundTag":"file-already-exists","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}