juicedata/juicefs · error

failed to marshal checkpoint

Error message

failed to marshal checkpoint: %w

What it means

Save() serializes the current checkpoint (after collecting prefix states under locks) with json.Marshal. This error wraps a json.Marshal failure, meaning some field of the Checkpoint cannot be represented in JSON (e.g. unsupported type such as a channel/func, invalid number like NaN, or a custom MarshalJSON error). Save aborts and the checkpoint is not persisted, so progress since the last successful save would be lost on interruption.

Solutions

  1. Inspect the wrapped error to find the offending field; make it JSON-serializable (use plain types, fix NaN/Inf values)
  2. Check recent changes to the Checkpoint/multipartUploadState/PrefixState structs for non-serializable fields
  3. Add a marshal step test (TestCheckpointManagerSaveAndLoad covers round-trip) and fix the field type

Example fix

// before
Copied float64 // may hold NaN
// after: guard before saving
if math.IsNaN(ckpt.Stats.Copied) {
	ckpt.Stats.Copied = 0
}
data, err := json.Marshal(ckpt)
Defensive patterns

Strategy: try-catch

Try / catch

if err := mgr.Save(ctx); err != nil {
	if strings.Contains(err.Error(), "failed to marshal checkpoint") {
		logger.Errorf("checkpoint not persisted: %v", err) // inspect field named in the error
	}
}

Prevention

When it happens

Trigger: Periodic checkpoint save (or explicit Save from tests/seedCheckpoint) executes json.Marshal(ckpt) and it returns an error — typically due to a non-JSON-serializable value added to Checkpoint/PrefixState fields, e.g. NaN/Inf floats in Stats or an unexpected field type introduced in a code change.

Common situations: Developers extending Checkpoint with a new field of a non-marshalable type; counters corrupted to NaN; concurrent mutation causing inconsistent state passed to the encoder.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/e801b3f31c9a89de. Report an issue: GitHub.

Appendix: source

Thrown at pkg/sync/checkpoint.go:300

	for _, state := range ckpt.PrefixState {
		state.RLock()
	}
	m.multipartUploadStore.RLock()
	srcDelayDelMu.Lock()
	ckpt.SrcDelayDel = append([]string(nil), srcDelayDel...)
	srcDelayDelMu.Unlock()
	dstDelayDelMu.Lock()
	ckpt.DstDelayDel = append([]string(nil), dstDelayDel...)
	dstDelayDelMu.Unlock()
	data, err := json.Marshal(ckpt)
	m.multipartUploadStore.RUnlock()
	for _, state := range ckpt.PrefixState {
		state.RUnlock()
	}
	ckpt.Unlock()

	if err != nil {
		return fmt.Errorf("failed to marshal checkpoint: %w", err)
	}

	logger.Debugf("Saving checkpoint with %d prefixes, copied: %d, failed: %d",
		prefixCount, ckpt.Stats.Copied, ckpt.Stats.Failed)
	reader := bytes.NewReader(data)
	if err := m.dst.Put(ctx, m.checkpointKey, reader); err != nil {
		return fmt.Errorf("failed to put checkpoint: %w", err)
	}

	return nil
}

// ValidateConfig checks if checkpoint config matches current config
func (m *CheckpointManager) ValidateConfig(current *Config) bool {
	old := m.checkpoint.Config

	// Must match fields
	if old.Start != current.Start || old.End != current.End {

View on GitHub (pinned to c9a67b23e8)