Tencent/WeKnora · error

consume sandbox turn rebuild: %w

Error message

consume sandbox turn rebuild: %w

What it means

ConsumeTurnRebuild runs a Lua script that atomically spends the single stale-sandbox rebuild allowed for the current turn in the Redis turn-lease hash. This error wraps any failure of that script execution (connection error, script error, NOSCRIPT, context cancellation). It is thrown because silently skipping consumption could let a later mid-turn install tear down a sandbox that in-flight work depends on.

Source

Thrown at internal/sandbox/session_binding_redis.go:375

	refs, _ := strconv.Atoi(values["refs"])
	if refs <= 0 {
		return false, false, nil
	}
	return true, values["rebuild"] == "1", nil
}

// ConsumeTurnRebuild spends the one rebuild allowed for the current turn.
func (s *RedisSessionSandboxBindingStore) ConsumeTurnRebuild(
	ctx context.Context,
	key SessionSandboxKey,
) error {
	if err := key.Validate(); err != nil {
		return err
	}
	if err := consumeTurnRebuildScript.Run(
		ctx, s.client, []string{s.turnKey(key)}, sessionTurnLeaseTTL.Milliseconds(),
	).Err(); err != nil {
		return fmt.Errorf("consume sandbox turn rebuild: %w", err)
	}
	return nil
}

func (s *RedisSessionSandboxBindingStore) turnKey(key SessionSandboxKey) string {
	return "weknora:sandbox:session:{" + s.hashTag(key) + "}:turn"
}

func (s *RedisSessionSandboxBindingStore) bindingKey(key SessionSandboxKey) string {
	return "weknora:sandbox:session:{" + s.hashTag(key) + "}:binding"
}

func (s *RedisSessionSandboxBindingStore) lockKey(key SessionSandboxKey) string {
	// Keep the historical suffix used by the saved multi-node Cube
	// implementation so rolling upgrades serialize on the same lock.
	return "weknora:sandbox:session:{" + s.hashTag(key) + "}:create-lock"
}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Verify Redis availability and connectivity; fix connection/auth config.
  2. Unwrap the error chain: if it is a retryable Redis I/O error, retry the operation — consuming the rebuild is idempotent in effect per turn.
  3. Check Redis memory (maxmemory) if OOM errors are wrapped, and raise limits or enable eviction policy appropriate for these keys.
  4. Ensure all WeKnora nodes load the same script version (matching deployments) to avoid persistent script mismatches.

Example fix

// before
if err := store.ConsumeTurnRebuild(ctx, key); err != nil {
    return err
}
// after
if err := store.ConsumeTurnRebuild(ctx, key); err != nil {
    if isRetryableRedisErr(err) { // e.g. io.EOF, net timeout, redis.Nil-free I/O errors
        if retryErr := store.ConsumeTurnRebuild(ctx, key); retryErr != nil {
            return fmt.Errorf("consume sandbox turn rebuild: %w", retryErr)
        }
    } else {
        return err
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if err := rdb.Ping(ctx).Err(); err != nil {
    return fmt.Errorf("redis unavailable before ConsumeTurnRebuild: %w", err)
}

Type guard

func isRetryableRedisErr(err error) bool {
    var rerr *redis.RedisError
    return errors.As(err, &rerr) && !strings.Contains(err.Error(), "NOSCRIPT")
}

Try / catch

if err := store.ConsumeTurnRebuild(ctx, key); err != nil {
    if isRetryableRedisErr(err) {
        err = store.ConsumeTurnRebuild(ctx, key)
    }
    if err != nil { return fmt.Errorf("consume rebuild: %w", err) }
}

Prevention

When it happens

Trigger: RedisSessionSandboxBindingStore.ConsumeTurnRebuild is called (from resolveLocked after stale handling) when Redis is down, the EVALSHA/EVAL call fails, the script returns an error, or ctx is cancelled during the round trip.

Common situations: Redis failover mid-request; Redis SCRIPT FLUSH causing transient NOSCRIPT (usually auto-retried by go-redis, but surfaces if retries are disabled); network blips; Redis OOM refusing writes.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/fdfe57c291cd6294. Report an issue: GitHub.