siyuan-note/siyuan · error

mobile export lease [%s] already exists

Error message

mobile export lease [%s] already exists

What it means

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.

Source

Thrown at kernel/model/encrypted_export.go:378

}

func registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {
	info, err := os.Stat(artifact)
	if err != nil {
		return nil, err
	}
	if info.IsDir() {
		return nil, fmt.Errorf("export artifact [%s] is a directory", artifact)
	}
	state := &mobileExportLeaseState{
		boxID:      boxID,
		cleanupDir: cleanupDir,
		expiresAt:  time.Now().Add(mobileExportLeaseTTL),
	}
	mobileExportLeases.Lock()
	if _, exists := mobileExportLeases.leases[leaseID]; exists {
		mobileExportLeases.Unlock()
		return nil, fmt.Errorf("mobile export lease [%s] already exists", leaseID)
	}
	mobileExportLeases.leases[leaseID] = state
	state.timer = time.AfterFunc(mobileExportLeaseTTL, func() {
		releaseMobileExportLease(leaseID, true)
	})
	mobileExportLeases.Unlock()
	return &MobileExportLease{ID: leaseID, Path: artifact, Name: name, Size: info.Size()}, nil
}

// ReleaseExportArtifactLease 释放导出产物租约;重复调用不会产生副作用。
func ReleaseExportArtifactLease(leaseID string) {
	releaseMobileExportLease(leaseID, false)
}

// ReleaseMobileExportLease 释放移动端导出租约;重复调用不会产生副作用。
func ReleaseMobileExportLease(leaseID string) {
	ReleaseExportArtifactLease(leaseID)
}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Generate a fresh unique lease ID (random/UUID) per request instead of reusing an existing one.
  2. Release the prior lease first (call the release API) or wait for the TTL expiry, then re-acquire.
  3. Treat this as a benign conflict: catch it and fetch/reuse the already-existing lease rather than re-registering.

Example fix

// before
lease, err := model.AcquireMobileExportLeaseWithID("lease-1", boxID, artifact)
// after
leaseID := uuid.NewString()
lease, err := model.AcquireMobileExportLeaseWithID(leaseID, boxID, artifact)
Defensive patterns

Strategy: try-catch

Try / catch

lease, err := registerMobileExportLeaseWithID(id, boxID, artifact, name, dir)
if err != nil && strings.Contains(err.Error(), "already exists") {
    lease = getExistingLease(id) // reuse instead of re-registering
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/4f845d89db0fc708. Report an issue: GitHub.