Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/TypeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ public function mapAnnotatedObject(string $annotatedObjectClassName): MutableInt
$type->description = $resolvedDescription;
}

// Instantiating the annotated object via the container (above) can reentrantly resolve and
// register this very type — e.g. a dependency in the container graph references it. If so,
// reuse that instance rather than return the duplicate we just built, which would later trip
// the registry identity check in RecursiveTypeMapper (the first-request crash in long-lived
// workers). mapFactoryMethod() guards its own cache the same way after its container->get.
// See https://github.com/thecodingmachine/graphqlite/issues/531
if ($this->typeRegistry->hasType($type->name)) {
return $this->typeRegistry->getMutableInterface($type->name);
}

return $type;
}

Expand Down
32 changes: 32 additions & 0 deletions tests/TypeGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,36 @@ public function testextendAnnotatedObjectException(): void
$this->expectException(MissingAnnotationException::class);
$typeGenerator->extendAnnotatedObject(new stdClass(), $type);
}

public function testMapAnnotatedObjectReusesTypeRegisteredReentrantlyDuringContainerGet(): void
{
// Reproduces the cold-registry race behind #531: instantiating the annotated object via the
// container reentrantly resolves (and registers) this same type. mapAnnotatedObject must
// reuse that instance rather than build a duplicate — otherwise RecursiveTypeMapper's
// identity check throws "Cached type in registry is not the type returned by type mapper."
// on the first request in long-lived workers (Swoole/RoadRunner/FrankenPHP).
$typeRegistry = $this->getTypeRegistry();
$reentrantlyRegistered = new MutableObjectType(['name' => 'TestObject', 'fields' => []], TypeFoo::class);

$container = new LazyContainer([
TypeFoo::class => static function () use ($typeRegistry, $reentrantlyRegistered) {
if (! $typeRegistry->hasType('TestObject')) {
$typeRegistry->registerType($reentrantlyRegistered);
}

return new TypeFoo();
},
]);

$typeGenerator = new TypeGenerator(
$this->getAnnotationReader(),
new NamingStrategy(),
$typeRegistry,
$container,
$this->getTypeMapper(),
$this->getFieldsBuilder(),
);

$this->assertSame($reentrantlyRegistered, $typeGenerator->mapAnnotatedObject(TypeFoo::class));
}
}
Loading