Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.

Commit 36b544d

Browse files
committed
Defer IDGenerator initialization until first use
Initializing the `IDGenerator` from `init()` means that any downstream project which transitively imports this package somewhere in its dependency tree will incur `getrandom()` syscalls it has no control over at startup. This causes problems for us in [Ignition](https://github.com/coreos/ignition), where we're transitively pulling in this package via cloud.google.com/go/storage. Ignition runs very early during the boot process, which means that even though this isn't using `GRND_RANDOM`, the `getrandom` syscall can block until the entropy pool is ready. This is a real problem when running in VMs on systems which don't provide a virtualized RNG device (such as VMware) or which lack RDRAND. I can't find a good reference for this, but I think in general it should be considered good practice to avoid I/O like this in `init()` in favour of a more lazy approach (or an explicit `Initialize()` function for clients to call). Otherwise, *every* program which pulls in the package will pay for it, whether or not they intend to actually make use of the functionality those syscalls are priming. (While it's not relevant here, another advantage of not using `init()` for this is that I/O is fallible, and `init()` semantics means errors can't be handled sanely.) Let's rework things here so that we don't actually initialize the `IDGenerator` fields until the first time it's used.
1 parent d7677d6 commit 36b544d

File tree

1 file changed

+26
-12
lines changed

1 file changed

+26
-12
lines changed

trace/trace.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ func startSpanInternal(name string, hasParent bool, parent SpanContext, remotePa
206206
span.spanContext = parent
207207

208208
cfg := config.Load().(*Config)
209+
gen := cfg.IDGenerator.(*defaultIDGenerator)
210+
gen, ok := cfg.IDGenerator.(*defaultIDGenerator)
211+
if ok {
212+
// lazy initialization
213+
gen.init()
214+
}
209215

210216
if !hasParent {
211217
span.spanContext.TraceID = cfg.IDGenerator.NewTraceID()
@@ -534,20 +540,9 @@ func (s *Span) String() string {
534540
var config atomic.Value // access atomically
535541

536542
func init() {
537-
gen := &defaultIDGenerator{}
538-
// initialize traceID and spanID generators.
539-
var rngSeed int64
540-
for _, p := range []interface{}{
541-
&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc,
542-
} {
543-
binary.Read(crand.Reader, binary.LittleEndian, p)
544-
}
545-
gen.traceIDRand = rand.New(rand.NewSource(rngSeed))
546-
gen.spanIDInc |= 1
547-
548543
config.Store(&Config{
549544
DefaultSampler: ProbabilitySampler(defaultSamplingProbability),
550-
IDGenerator: gen,
545+
IDGenerator: &defaultIDGenerator{},
551546
MaxAttributesPerSpan: DefaultMaxAttributesPerSpan,
552547
MaxAnnotationEventsPerSpan: DefaultMaxAnnotationEventsPerSpan,
553548
MaxMessageEventsPerSpan: DefaultMaxMessageEventsPerSpan,
@@ -571,6 +566,25 @@ type defaultIDGenerator struct {
571566

572567
traceIDAdd [2]uint64
573568
traceIDRand *rand.Rand
569+
570+
initOnce sync.Once
571+
}
572+
573+
574+
// init initializes the generator on the first call to avoid consuming entropy
575+
// unnecessarily.
576+
func (gen *defaultIDGenerator) init() {
577+
gen.initOnce.Do(func(){
578+
// initialize traceID and spanID generators.
579+
var rngSeed int64
580+
for _, p := range []interface{}{
581+
&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc,
582+
} {
583+
binary.Read(crand.Reader, binary.LittleEndian, p)
584+
}
585+
gen.traceIDRand = rand.New(rand.NewSource(rngSeed))
586+
gen.spanIDInc |= 1
587+
})
574588
}
575589

576590
// NewSpanID returns a non-zero span ID from a randomly-chosen sequence.

0 commit comments

Comments
 (0)