Skip to content

Context is not actually immutable #2044

Description

@SamMousa

Steps to reproduce
The specification for context: https://github.com/open-telemetry/opentelemetry-specification/tree/v1.44.0/specification/context

Requires a Context instance to be strictly immutable. The current implementation fails this promise.

What is the expected behavior?
Any supported value in Context::with() should either be deeply cloned, or immutable.

What is the actual behavior?

#[\Override]
    public function with(ContextKeyInterface $key, $value): self
    {
        if ($this->get($key) === $value) {
            return $this;
        }

        $self = clone $this;

        if ($key === self::$spanContextKey) {
            $self->span = $value; // @phan-suppress-current-line PhanTypeMismatchPropertyReal

            return $self;
        }

        $id = spl_object_id($key);
        if ($value !== null) {
            $self->context[$id] = $value;
            $self->contextKeys[$id] ??= $key;
        } else {
            unset(
                $self->context[$id],
                $self->contextKeys[$id],
            );
        }

        return $self;
    }

On clone the array is cloned, but the values are copied. Any object typed values are therefore not cloned.
This means that storing a mutable object could lead to unexpected behavior:

$context = Context::getCurrent()
$key = Context::createKey("test");

$mutableValue = new ArrayObject(['message' => 'hello world']);
$context1 = $context->with($key, $mutableValue);

echo $context1->get($key)['message']; // hello world
$mutableValue['message'] = 'bye';

echo $context1->get($key)['message']; // bye

$key2 = Context::createKey("test2");

$context2 = $context->with($key2, 'test123');
echo $context2->get($key)['message']; // bye
/**
 * meanwhile in another Fiber or any other part of the code:
 */
$mutableValue['message'] = 'hello world';
 
echo $context2->get($key)['message']; // hello world

While I believe that case one could be functioning as intended, I believe case 2 is definitely not what people will expect.
I believe this is not technically solvable in PHP.

If we define a Context to be a bag of keys and values, where keys are opaque identifiers (ContextKey), one could argue that we maybe should just not support non-scalar values.

The least we should do is put a comment in ContextInterface to explain the limitations and deviations from the spec (if you consider this a deviation).

Additional context
This is more of a theoretical problem than a practical one at this point.

Tip: React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions