dgraph-io/dgraph · error

UpdateExtSnapshotStreamingStateRequest cannot have both Star

Error message

UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true

What it means

The Start and Finish flags of UpdateExtSnapshotStreamingStateRequest are mutually exclusive: Start turns import/streaming mode on, Finish turns it off. Setting both is ambiguous and rejected with this error before any Raft proposal is made.

Source

Thrown at worker/import.go:463

		}
		if _, err := out.Recv(); err != nil {
			return fmt.Errorf("ack data downstream(%d): %w", groupId, err)
		}
		if err := in.Send(&api.StreamExtSnapshotResponse{}); err != nil {
			return fmt.Errorf("send ack upstream: %w", err)
		}

	}
}

func (w *grpcWorker) UpdateExtSnapshotStreamingState(ctx context.Context,
	req *api.UpdateExtSnapshotStreamingStateRequest) (*pb.Status, error) {
	if req == nil {
		return nil, errors.New("UpdateExtSnapshotStreamingStateRequest must not be nil")
	}

	if req.Start && req.Finish {
		return nil, errors.New("UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true")
	}

	glog.Infof("[import] Applying import mode proposal: %+v", req)
	err := groups().Node.proposeAndWait(ctx, &pb.Proposal{ExtSnapshotState: req})

	return &pb.Status{}, err
}

// StreamExtSnapshot handles the stream of key-value pairs sent from proxy alpha.
// It receives a Forward flag from the stream to determine if the current node is the leader.
// If the node is the leader (Forward is true), it streams the data to its followers.
// Otherwise, it simply writes the data to BadgerDB and flushes it.
func (w *grpcWorker) StreamExtSnapshot(stream pb.Worker_StreamExtSnapshotServer) error {
	glog.Info("[import] trying to update the import mode to false")
	defer x.ExtSnapshotStreamingState(false)

	// Receive the first message to check the Forward flag.
	// If Forward is true, this node is the leader and should forward the stream to its followers.

View on GitHub (pinned to 759e242be6)

Solutions

  1. Send two separate calls: one with Start=true, later one with Finish=true.
  2. Reset the struct before reuse: &api.UpdateExtSnapshotStreamingStateRequest{Start: true}.
  3. Validate flags client-side before invoking the RPC.

Example fix

// before
req := &api.UpdateExtSnapshotStreamingStateRequest{Start: prev.Finish}
req.Start, req.Finish = true, true
// after
req := &api.UpdateExtSnapshotStreamingStateRequest{Finish: true}
Defensive patterns

Strategy: validation

Validate before calling

if req != nil && req.Start && req.Finish {
    return errors.New("Start and Finish are mutually exclusive; send two separate requests")
}

Type guard

func isAmbiguousStateReq(req *api.UpdateExtSnapshotStreamingStateRequest) bool {
    return req != nil && req.Start && req.Finish
}

Try / catch

if _, err := wc.UpdateExtSnapshotStreamingState(ctx, req); err != nil {
    if strings.Contains(err.Error(), "both Start and Finish") { /* send separate Start and Finish calls */ }
}

Prevention

When it happens

Trigger: Sending an UpdateExtSnapshotStreamingStateRequest with both Start=true and Finish=true via the gRPC API, e.g. reusing a populated struct and only flipping one flag.

Common situations: Copy/reuse of a previous request struct without resetting fields; buggy client code computing flags; a mistaken attempt to 'restart' streaming in one call.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/b610fd54dd4947cf. Report an issue: GitHub.