ethereum/go-ethereum · error
can't create subscription after subscribe call has returned
Error message
can't create subscription after subscribe call has returned
What it means
Notifier.CreateSubscription panics when called after the subscribe RPC call has already returned (n.callReturned is set by takeSubscription when the server finishes handling the method). The subscription must be created synchronously inside the service method, before it returns, so the server can send the subscription ID back to the client.
Source
Thrown at rpc/subscription.go:124
mu sync.Mutex
sub *Subscription
buffer []any
callReturned bool
activated bool
}
// CreateSubscription returns a new subscription that is coupled to the
// RPC connection. By default subscriptions are inactive and notifications
// are dropped until the subscription is marked as active. This is done
// by the RPC server after the subscription ID is send to the client.
func (n *Notifier) CreateSubscription() *Subscription {
n.mu.Lock()
defer n.mu.Unlock()
if n.sub != nil {
panic("can't create multiple subscriptions with Notifier")
} else if n.callReturned {
panic("can't create subscription after subscribe call has returned")
}
n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)}
return n.sub
}
// Notify sends a notification to the client with the given data as payload.
// If an error occurs the RPC connection is closed and the error is returned.
func (n *Notifier) Notify(id ID, data any) error {
n.mu.Lock()
defer n.mu.Unlock()
if n.sub == nil {
panic("can't Notify before subscription is created")
} else if n.sub.ID != id {
panic("Notify with wrong ID")
}
if n.activated {
return n.send(n.sub, data)View on GitHub (pinned to 6bb0588ad8)
Solutions
- Move CreateSubscription into the service method body, before it returns.
- If creation depends on async work, do the work first (or synchronously) and then create the subscription before returning.
- Do not store or reuse a Notifier across requests; request a fresh one via api.Notifier(...) in each call.
Example fix
// before
func (s *API) Subscribe(r *http.Request) (*rpc.Subscription, error) {
notifier, _ := s.api.Notifier(r.Context())
go func() { s.sub = notifier.CreateSubscription() }() // runs after return -> panic
...
}
// after
func (s *API) Subscribe(r *http.Request) (*rpc.Subscription, error) {
notifier, _ := s.api.Notifier(r.Context())
sub := notifier.CreateSubscription() // inside the call
go func() { /* only notify here */ }()
return sub, nil
} Defensive patterns
Strategy: validation
Prevention
- Keep CreateSubscription inside the synchronous body of the subscribe method.
- Never capture a Notifier in goroutines or store it for later use; obtain a fresh Notifier per request.
- Start background work only after the subscription is created and the method is about to return.
When it happens
Trigger: Capturing the *Notifier in a closure or storing it on the service struct, then calling CreateSubscription from a background goroutine after the subscribe method returned; calling CreateSubscription from inside notifier.Notify's send path or a later request.
Common situations: Starting a goroutine in the subscribe method that lazily creates the subscription; keeping the Notifier for a later handshake; refactoring a handler so the subscription creation moved outside the request scope.
Related errors
- can't create multiple subscriptions with Notifier
- can't Notify before subscription is created
- Notify with wrong ID
- channel given to Subscribe must not be nil
- nil TextMapPropagator configured
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/98a89e676525c1a1.
Report an issue: GitHub.