Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate tag.Map API #59

Closed
jmacd opened this issue Jul 17, 2019 · 2 comments · Fixed by #89
Closed

Eliminate tag.Map API #59

jmacd opened this issue Jul 17, 2019 · 2 comments · Fixed by #89
Labels
pkg:API Related to an API package
Milestone

Comments

@jmacd
Copy link
Contributor

jmacd commented Jul 17, 2019

The Map API is unsafe, as explained by @cstockton in #23 (comment)

Make it a concrete type.

@jmacd jmacd added the pkg:API Related to an API package label Jul 17, 2019
@krnowak
Copy link
Member

krnowak commented Aug 9, 2019

I'd like to pick up that task.

The question I have is what was the reason for having tag.Map as an interface? Was it to make it possible to have a different implementation of the interface from a different vendor of OT SDK?

From what I have understood from the comment is that the current implementation of the map is not thread-safe and it is not feasible to require thread-safety from the vendor implementations. Is this right?

If being able to provide an different tag.Map implementation by some vendor is still important then maybe it would be possible to achieve the thread safety by something like this:

type Map interface {
	Apply(update MapUpdate) Map
	…
}

type lockedMap struct {
	impl Map
	implLock sync.Mutex
}

var _ Map = &lockedMap{}

func (t &lockedMap) Apply(update MapUpdate) Map {
	t.implLock.Lock()
	defer t.implLock.Unlock()
	return t.impl.Apply(update)
}

// same for the rest of the Map interface methodsfunc NewEmptyMap() Map {
	return &lockedMap{
		impl: tagMap{}
	}
}

So in the end, vendor would need to provide an implementation of the tagMap type.

krnowak added a commit to kinvolk/opentelemetry-go that referenced this issue Aug 12, 2019
This adds a lockedMap type which implements tag.Map interface. The
implementation contains tagMap instance and a mutex, so all the
operations are forwarded to the tagMap under the locked mutex.

An additional care was needed for the functions returning contents of
the map, because core.Value contains a byte slice, which has pointer
like semantics. So to avoid accidental changes, we copy the value if
it is of BYTES type. This likely should be handled by the core.Value
itself, e.g. through some Copy function.

The downside of locking here is that the users of Foreach function
need to be careful to not call into the same map, otherwise deadlock
will happen.

While writing this code I think I have got an understanding of the
issue at hand - the implementation of the tag.Map should basically be
immutable (so the modification of the map actually produces a new map,
instead of doing in-place updates, thus making the map threadsafe),
but that is not easy (or even possible) to enforce. So maybe it's
indeed better to avoid providing the ability of having a different,
vendor-specific implementation of tag.Map and have a good-by-default
implemetation as a part of API.

Still, to preserve the immutability of the map, the core.Value structs
need to be deep-copied.

Fixes open-telemetry#59
@jmacd
Copy link
Contributor Author

jmacd commented Aug 12, 2019

I'm not sure there was any reason to have a tag.Map interface in the first place. As I think we've discovered in #52 (see also open-telemetry/oteps#11), there will be uses of the tag.Map interface that are expected before the SDK loads. So, I think we should eliminate the interface and use a concrete type. Then, having an immutable map is sufficient to avoid the problems with map concurrency.

I believe the point about deep-copying bytes is also true, but I want to point out some related work. I have an as-yet-unwritten proposal about supporting structured values and lazy values, for various reasons covered in open-telemetry/opentelemetry-specification#76. The code (in Go) is a proof-of-concept: jmacd#3
see the Key.Struct() and Key.Encode() APIs. There, I included an Evaluate API to ensure that lazy and structure-valued values are copied into bytes before they can be modified. I think you're saying that this should be the case for []byte in the present code base, and I agree.

krnowak added a commit to kinvolk/opentelemetry-go that referenced this issue Aug 13, 2019
This is to make tag.Map an immutable type, so it is safe to use
concurrently. The safety is not yet fully achieved because of the
functions returning contents of the map (Value and Foreach). The
functions give callers an access to core.Value objects, which contain
a byte slice, which has pointer like semantics. So to avoid accidental
changes, we will need to copy the value if it is of BYTES type.

Fixes open-telemetry#59
krnowak added a commit to kinvolk/opentelemetry-go that referenced this issue Aug 23, 2019
This is to make tag.Map an immutable type, so it is safe to use
concurrently. The safety is not yet fully achieved because of the
functions returning contents of the map (Value and Foreach). The
functions give callers an access to core.Value objects, which contain
a byte slice, which has pointer like semantics. So to avoid accidental
changes, we will need to copy the value if it is of BYTES type.

Fixes open-telemetry#59
rghetia pushed a commit that referenced this issue Aug 23, 2019
This is to make tag.Map an immutable type, so it is safe to use
concurrently. The safety is not yet fully achieved because of the
functions returning contents of the map (Value and Foreach). The
functions give callers an access to core.Value objects, which contain
a byte slice, which has pointer like semantics. So to avoid accidental
changes, we will need to copy the value if it is of BYTES type.

Fixes #59
@pellared pellared added this to the untracked milestone Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg:API Related to an API package
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants