cloudreve/cloudreve · error

failed to decrease reference count for entities %v: %w

Error message

failed to decrease reference count for entities %v: %w

What it means

Raised inside File.Delete step 1: after collecting how many times each entity is referenced by the files being deleted, it runs grouped UPDATE entities SET reference_count = reference_count - N statements. Failure means one of these atomic decrements did not execute (driver error, constraint, deadlock).

Source

Thrown at inventory/file.go:547

			entities[e.ID]++
			storageReduced[fi.OwnerID] -= e.Size
		}
	}

	// Group entities by their reference count.
	uniqueEntities := lo.Keys(entities)
	entitiesGrouped := lo.GroupBy(uniqueEntities, func(e int) int {
		return entities[e]
	})

	for ref, entityGroup := range entitiesGrouped {
		entityPageGroup, _ := f.batchInConditionEntityID(intsets.MaxInt, 10, 1, entityGroup)
		for _, group := range entityPageGroup {
			if err := f.client.Entity.Update().
				Where(group).
				AddReferenceCount(-1 * ref).
				Exec(ctx); err != nil {
				return nil, nil, fmt.Errorf("failed to decrease reference count for entities %v: %w", group, err)
			}
		}
	}

	// 2. Filter out entities with <=0 reference count, Update recycle options for above entities;
	entityGroup, _ := f.batchInConditionEntityID(intsets.MaxInt, 10, 1, uniqueEntities)
	toBeRecycled := make([]*ent.Entity, 0, len(entities))
	for _, group := range entityGroup {
		e, err := f.client.Entity.Query().Where(group).Where(entity.ReferenceCountLTE(0)).All(ctx)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to query orphan entities %v: %w", group, err)
		}

		toBeRecycled = append(toBeRecycled, e...)
	}

	// 3. Update recycle options for above entities;
	pageSize := capPageSize(f.maxSQlParam, intsets.MaxInt, 10)

View on GitHub (pinned to 20c95ad73f)

Solutions

  1. Unwrap and classify: retry on deadlock/lock-wait errors, fix connectivity otherwise
  2. Serialize large deletes per entity or run them in a transaction so decrements are consistent
  3. Make the GC job tolerate reference counts that briefly lag (it re-scans next cycle)
  4. Ensure the entities table PK/indexes are healthy so the UPDATE IN (...) is fast

Example fix

// before
recycled, diff, err := inv.Delete(ctx, files, props)

// after
var recycled []*ent.Entity
var diff inventory.StorageDiff
err := retry.OnDeadlock(func() error {
    recycled, diff, err = inv.Delete(ctx, files, props)
    return err
})
Defensive patterns

Strategy: retry

Try / catch

if _, _, err := inv.Delete(ctx, files, props); err != nil {
    if isDeadlock(err) {
        time.Sleep(50 * time.Millisecond)
        _, _, err = inv.Delete(ctx, files, props)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Concurrent Delete/Copy operations touching the same entity rows cause InnoDB lock wait timeout or deadlock; the entities row was hard-deleted by the GC between the read and the update (writes affect 0 rows, not an error, but FK errors can surface); connection/context failure mid-batch.

Common situations: Two users deleting files that share a deduplicated entity simultaneously; delete racing the recycle/GC job that purges stale entities; large batch crossing maxSQL param grouping with a dropped connection.

Related errors


AI-assisted analysis of cloudreve/cloudreve@20c95ad73f (2026-08-16). Data as JSON: /api/errors/08489d74ca2a37b8. Report an issue: GitHub.