pkg/xcontext/fields/pending_fields.go (73 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package fields
// PendingField is a one entry of a history of added fields.
//
// PendingField is focused on high-performance Clone method, for details
// see PendingFields.
type PendingField struct {
parentPendingFields Slice
// In current implementation oneFieldKey and multipleFields are never
// used together, but still we preserve oneFieldKey and oneFieldValue due
// to performance reasons (see the description of PendingFields), it
// allows to avoid allocating and accessing a map for a single field.
oneFieldKey string
oneFieldValue interface{}
multipleFields map[string]interface{}
}
// Slice is a set of entries of a history of added fields.
//
// See PendingFields.
type Slice []PendingField
// PendingFields is a history of added fields. It is focused on cheap
// cloning and adding new entries avoiding extra copy all the data on each
// adding of key-values. Therefore it is so-so performance-safe to
// use it for scoped contexts.
//
// A resulting value could be received (compiled) on-need-basis using method Compile.
//
// See also benchmark comments in pending_fields_test.go.
type PendingFields struct {
Slice Slice
IsReadOnly bool
}
// Clone returns a "copy" of PendingFields. It is safe to add new entries
// to this "copy" (it will not affect the original).
func (pendingFields PendingFields) Clone() PendingFields {
if pendingFields.Slice != nil {
pendingFields.IsReadOnly = true
}
return pendingFields
}
// Compile constructs Fields from PendingFields.
func (pendingFields PendingFields) Compile() Fields {
return pendingFields.Slice.compile(nil)
}
// CompileWithStorage is the same as Compile, but allows to pass a storage
// to save values to. It could be used, for example, to reuse existing storages
// to reduce pressure on GC.
func (pendingFields PendingFields) CompileWithStorage(m Fields) Fields {
return pendingFields.Slice.compile(m)
}
func (pendingFields Slice) compile(result Fields) Fields {
for _, item := range pendingFields {
switch {
case item.parentPendingFields != nil:
if result == nil {
result = make(Fields, len(pendingFields)+len(item.parentPendingFields)-1)
}
item.parentPendingFields.compile(result)
case item.multipleFields != nil:
if result == nil {
result = item.multipleFields
continue
}
for k, v := range item.multipleFields {
result[k] = v
}
default:
if result == nil {
result = make(Fields, len(pendingFields))
}
result[item.oneFieldKey] = item.oneFieldValue
}
}
return result
}
// AddOne adds one field.
func (pendingFields *PendingFields) AddOne(key string, value interface{}) {
if pendingFields.IsReadOnly {
newPendingFields := PendingFields{Slice: make([]PendingField, 1, 2)}
newPendingFields.Slice[0] = PendingField{parentPendingFields: pendingFields.Slice}
*pendingFields = newPendingFields
}
pendingFields.Slice = append(pendingFields.Slice, PendingField{
oneFieldKey: key,
oneFieldValue: value,
})
}
// AddMultiple adds a set of fields.
func (pendingFields *PendingFields) AddMultiple(fields Fields) {
if fields == nil {
return
}
if pendingFields.IsReadOnly {
newPendingFields := PendingFields{Slice: make([]PendingField, 1, 2)}
newPendingFields.Slice[0] = PendingField{parentPendingFields: pendingFields.Slice}
*pendingFields = newPendingFields
}
pendingFields.Slice = append(pendingFields.Slice, PendingField{
multipleFields: fields,
})
}