wavetermdev/waveterm · warning

flush already in progress

Error message

flush already in progress

What it means

FlushCache is single-flight per FileStore: an atomic setUnlessFlushing guard ensures only one flush runs at a time. Calling FlushCache while a previous flush (e.g. started by the background runFlushWithNewContext loop) is still in progress returns this error immediately instead of queueing a second flush.

Source

Thrown at pkg/filestore/blockstore.go:399

// returns (offset, data, error)
func (s *FileStore) ReadFile(ctx context.Context, zoneId string, name string) (rtnOffset int64, rtnData []byte, rtnErr error) {
	withLock(s, zoneId, name, func(entry *CacheEntry) error {
		rtnOffset, rtnData, rtnErr = entry.readAt(ctx, 0, 0, true)
		return nil
	})
	return
}

type FlushStats struct {
	FlushDuration   time.Duration
	NumDirtyEntries int
	NumCommitted    int
}

func (s *FileStore) FlushCache(ctx context.Context) (stats FlushStats, rtnErr error) {
	wasFlushing := s.setUnlessFlushing()
	if wasFlushing {
		return stats, fmt.Errorf("flush already in progress")
	}
	defer s.setIsFlushing(false)
	startTime := time.Now()
	defer func() {
		stats.FlushDuration = time.Since(startTime)
	}()

	// get a copy of dirty keys so we can iterate without the lock
	dirtyCacheKeys := s.getDirtyCacheKeys()
	stats.NumDirtyEntries = len(dirtyCacheKeys)
	for _, key := range dirtyCacheKeys {
		err := withLock(s, key.ZoneId, key.Name, func(entry *CacheEntry) error {
			return entry.flushToDB(ctx, false)
		})
		if ctx.Err() != nil {
			// transient error (also must stop the loop)
			return stats, ctx.Err()
		}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Treat this error as benign: skip or retry after a short delay, since a flush is already running.
  2. Coordinate with the internal flush loop — don't call FlushCache manually if runFlushWithNewContext is enabled.
  3. Serialize flush triggers through a single goroutine/channel in your app.

Example fix

// before
if err := fs.FlushCache(ctx); err != nil { return err }
// after
if _, err := fs.FlushCache(ctx); err != nil && !strings.Contains(err.Error(), "flush already in progress") {
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

stats, err := fs.FlushCache(ctx)
if err != nil && strings.Contains(err.Error(), "flush already in progress") {
    return nil // benign: a flush is already running
} else if err != nil {
    return err
}

Prevention

When it happens

Trigger: Calling FlushCache from application code while the internal flush loop (runFlushWithNewContext) or another goroutine is already flushing; calling FlushCache concurrently from multiple request handlers.

Common situations: Shutdown hooks calling FlushCache at the same time as the periodic flusher; load tests hammering FlushCache concurrently; a long flush (many dirty entries) overlapping a manual flush.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/6999ebd0b9ce98d5. Report an issue: GitHub.