Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

foreach over ObjectId and Garbage Collection #1776

Open
Labels
tracked-in-jiraTicket filed in Mongo's Jira system
@uh-zuh

Description

Bug Report

foreach over ObjectId changes behavior after circular reference collector.

Before GC foreach over ObjectId executes one loop iteration with $key="oid".
After GC foreach over ObjectId executes zero loop iterations.

Environment

PHP inside Docker on Windows 10.
Docker Image: php:8.2.27-apache
PHP: 8.2.27
MongoDB PHP Driver: 1.20.1
MongoDB PHP Library: 1.20.0
MongoDB: 8.0.0
mongodb/laravel-mongodb: 4.8.1

MongoDB: I run DB in separate Docker container locally and use in PHP only HOST and PORT of DB.

www-data@5c2174446320:~/html$ php -i | grep -E 'mongodb|libmongoc|libbson'
Cannot load Xdebug - it was already loaded
/usr/local/etc/php/conf.d/mongodb.ini,
mongodb
libbson bundled version => 1.28.1
libmongoc bundled version => 1.28.1
libmongoc SSL => enabled
libmongoc SSL library => OpenSSL
libmongoc crypto => enabled
libmongoc crypto library => libcrypto
libmongoc crypto system profile => disabled
libmongoc SASL => enabled
libmongoc SRV => enabled
libmongoc compression => enabled
libmongoc compression snappy => enabled
libmongoc compression zlib => enabled
libmongoc compression zstd => enabled
libmongocrypt bundled version => 1.11.0
libmongocrypt crypto => enabled
libmongocrypt crypto library => libcrypto
mongodb.debug => no value => no value

Test Script

I've used mongodb/laravel-mongodb to reproduce the bug but there is not so much Laravel specific and main issue in \MongoDB\BSON\ObjectId.

ObjectIdTestModel.php

<?php
namespace App\Models;
class ObjectIdTestModel extends \MongoDB\Laravel\Eloquent\Model
{
}

Test.php

\dump();
// Uncommenting following line fixes the test
//gc_disable();
$objectId1 = new ObjectId();
$objectId2 = new ObjectId();
// Here we see public property "oid"
echo 'objectId1: ';
xdebug_debug_zval('objectId1');
echo 'objectId2: ';
xdebug_debug_zval('objectId2');
// Check possible assumption about iterating
$is_traversable = ($objectId1 instanceof \Traversable);
echo 'is_traversable: ' . var_export($is_traversable, true) . "\n";
// Check there are no properties for iterating in foreach hence there is not property "oid"
$reflect = new \ReflectionClass($objectId1);
$properties = $reflect->getProperties();
echo 'properties: ' . print_r($properties, true);
// Allocate a lot of objects
for ($i = 0; $i < 200; $i++) {
 $model = new ObjectIdTestModel();
 $model->save();
}
$a = ObjectIdTestModel::all();
// First foreach over \MongoDB\BSON\ObjectId
$number1 = 0;
foreach ($objectId1 as $key => $value) {
 $number1 += 1;
}
// Allocate a lot of objects again
$a = ObjectIdTestModel::all();
// Second foreach over \MongoDB\BSON\ObjectId
$number2 = 0;
foreach ($objectId2 as $key => $value) {
 $number2 += 1;
}
echo 'objectId1: ';
xdebug_debug_zval('objectId1');
echo 'objectId2: ';
xdebug_debug_zval('objectId2');
$this->assertEquals(1, $number1, 'number1');
$this->assertEquals(1, $number2, 'number2');

Test output:

objectId1: objectId1: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f004' }
objectId2: objectId2: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f005' }
is_traversable: false
properties: Array
(
)
objectId1: objectId1: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f004' }
objectId2: objectId2: (refcount=1, is_ref=0)=class MongoDB\BSON\ObjectId { public $oid = (refcount=1, is_ref=0)='679a97b2d1cc47db6503f005' }
...
 number2
 Failed asserting that 0 matches expected 1.

If the bug is not repoduced you can increase number of created objects in line for ($i = 0; $i < 200; $i++) {

Expected and Actual Behavior

Expected

$number1 === 1
$number2 === 1
Circular reference collector do not change script behavior.

Actual

$number1 === 1
$number2 === 0
Calling gc_disable() "fixes" the bug.

Preffered solution

Prohibit to iterate over \MongoDB\BSON\ObjectId especially by hidden public property "oid".
I have iterated it by mistake and prefer empty loop over object without public properties instead of strange behavior.

Debug Log

object_id_gc.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    tracked-in-jiraTicket filed in Mongo's Jira system

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

        AltStyle によって変換されたページ (->オリジナル) /