diff --git a/api/global/internal/meter.go b/api/global/internal/meter.go index 15ca9833a86..e15d75feda6 100644 --- a/api/global/internal/meter.go +++ b/api/global/internal/meter.go @@ -233,6 +233,12 @@ func (bound *instHandle) RecordOne(ctx context.Context, number core.Number) { if implPtr == nil { implPtr = (*metric.BoundInstrumentImpl)(atomic.LoadPointer(&bound.delegate)) } + // This may still be nil if instrument was created and bound + // without a delegate, then the instrument was set to have a + // delegate and unbound. + if implPtr == nil { + return + } (*implPtr).RecordOne(ctx, number) } diff --git a/api/global/internal/meter_test.go b/api/global/internal/meter_test.go index 9c67d4ff474..0156d40a104 100644 --- a/api/global/internal/meter_test.go +++ b/api/global/internal/meter_test.go @@ -225,3 +225,23 @@ func TestDefaultSDK(t *testing.T) { require.Equal(t, `{"updates":[{"name":"test.builtin{A=B}","sum":1}]} `, <-ch) } + +func TestUnbindThenRecordOne(t *testing.T) { + internal.ResetForTest() + + // Note: this test uses oppsite Float64/Int64 number kinds + // vs. the above, to cover all the instruments. + ctx := context.Background() + sdk := metrictest.NewProvider() + meter := global.MeterProvider().Meter("test") + counter := meter.NewInt64Counter("test.counter") + boundC := counter.Bind(meter.Labels()) + global.SetMeterProvider(sdk) + boundC.Unbind() + + require.NotPanics(t, func() { + boundC.Add(ctx, 1) + }) + mock := global.MeterProvider().Meter("test").(*metrictest.Meter) + require.Equal(t, 0, len(mock.MeasurementBatches)) +}