Context Cancelation
In previous post we have a code:
func (s *service) DoSomethingWithContext(
ctx context.Context,
idA string,
idB string,
) (*model.SomeStatus, error) {
req := someapi.TriggerRequest{
FieldA: idA,
FieldB: idB,
}
noCancelCTX := nocancel.FromContext(ctx)
go func() {
// Defer cancelFunc to ensure the context lives through the RPC
newCtx, cancel := context.WithTimeout(noCancelCTX, 10*time.Minute)
defer cancel()
resp, err := s.client.TriggerAction(
newCtx,
connect.NewRequest(&req),
)
if err != nil {
telemetry.L(ctx).Error("Async action failed", zap.Error(err))
} else {
telemetry.L(ctx).Info("Async action succeeded", zap.Any("status", model.SomeStatus(resp.Msg.Status)))
}
}()
return ptr.To(model.SomeStatusStarted), nil
}
Why do we need to call defer cancel() ? In Go, when you create a context with context.WithTimeout, context.WithCancel or context.WithDeadline, the runtime sets up internal resources like timers and cancellation signals to track its lifecylce.
Detached Context
func (s *service) DoSomethingWithContext(
ctx context.Context,
idA string,
idB string,
) (*model.SomeStatus, error) {
req := someapi.TriggerRequest{
FieldA: idA,
FieldB: idB,
}
noCancelCTX := nocancel.FromContext(ctx)
go func() {
// Defer cancelFunc to ensure the context lives through the RPC
newCtx, cancel := context.WithTimeout(noCancelCTX, 10*time.Minute)
defer cancel()
resp, err := s.client.TriggerAction(
newCtx,
connect.NewRequest(&req),
)
if err != nil {
telemetry.L(ctx).Error("Async action failed", zap.Error(err))
} else {
telemetry.L(ctx).Info("Async action succeeded", zap.Any("status", model.SomeStatus(resp.Msg.Status)))
}
}()
return ptr.To(model.SomeStatusStarted), nil
}
1. noCancelCTX := nocancel.FromContext(ctx) It takes an existing context.Context (ctx) and returns a new context that is not canceled when the original one is. This is useful when you want to detach a background task from the lifecycle of the incoming request. ✅ Why use it? If ctx is tied to an HTTP request or RPC call, it will be canceled when the client disconnects or the request times out.By using nocancel.FromContext(ctx), you’re saying:
Transaction for Update
Introduction
(This is for Mysql. Postgresql has different mechanism like RETURNING)
func (s *Service) UnenrollMembership(
ctx context.Context,
accountID string,
) error {
s.membershipRepo.UnenrollMembership(
ctx,
accountID,
)
if err != nil {
return err
}
s.produceUnenrollmentEvent(ctx, accountID)
}
func (s *ChannelMembershipRepo) UnenrollChannelMembership(ctx context.Context, accountID string) error {
params := db.UpdateMembershipEndTimeParams{
AccountID: accountID,
EndAt: sql.NullTime{
Time: time.Now(),
Valid: true,
},
}
err := s.queries.UpdateMembershipEndTime(ctx, params)
if err != nil {
return err
}
return nil
}
/* name: UpdateEnrollmentChannelMembershipEndTime :exec */
-- UpdateEnrollmentChannelMembershipEndTime for any row of a given account_id with NULL end_time, updates the end_time
-- to the given time
UPDATE membership SET end_at = sqlc.arg(end_at) WHERE account_id = ? AND (end_at IS NULL OR end_at > sqlc.arg(end_at));
SQL code generated from sqlc query: