mongodb / mongo-php-driver

The Official MongoDB PHP driver
https://pecl.php.net/package/mongodb
Apache License 2.0
888 stars 202 forks source link

BulkWrite Error working with Swoole HTTP Server #925

Closed MassiAtZend closed 6 years ago

MassiAtZend commented 6 years ago

The following error 'BulkWrite objects may only be executed once and this instance has already been executed' is generated on every post after the first one (the first does insert data correctly) working with Swoole HTTP server.

The setup is 'docker' based, PHP7.2-cli image, using latest version on the extension and the PHP Library. See below:


php -i


/usr/local/etc/php/conf.d/docker-php-ext-mongodb.ini, mongodb libbson bundled version => 1.12.0 libmongoc bundled version => 1.12.0 libmongoc SSL => enabled libmongoc SSL library => OpenSSL libmongoc crypto => enabled libmongoc crypto library => libcrypto libmongoc crypto system profile => disabled libmongoc SASL => disabled libmongoc ICU => disabled libmongoc compression => enabled libmongoc compression snappy => disabled libmongoc compression zlib => enabled


composer.lock


{
            "name": "mongodb/mongodb",
            "version": "1.4.2",
            "source": {
                "type": "git",
                "url": "https://github.com/mongodb/mongo-php-library.git",
                "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/bd148eab0493e38354e45e2cd7db59b90fdcad79",
                "reference": "bd148eab0493e38354e45e2cd7db59b90fdcad79",
                "shasum": ""
            },
            "require": {
                "ext-hash": "*",
                "ext-json": "*",
                "ext-mongodb": "^1.5.0",
                "php": ">=5.5"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.8.36 || ^6.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "MongoDB\\": "src/"
                },
                "files": [
                    "src/functions.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "Jeremy Mikola",
                    "email": "jmikola@gmail.com"
                },
                {
                    "name": "Derick Rethans",
                    "email": "github@derickrethans.nl"
                },
                {
                    "name": "Katherine Walker",
                    "email": "katherine.walker@mongodb.com"
                }
            ],
            "description": "MongoDB driver library",
            "homepage": "https://jira.mongodb.org/browse/PHPLIB",
            "keywords": [
                "database",
                "driver",
                "mongodb",
                "persistence"
            ],
            "time": "2018-07-18T14:33:41+00:00"
        },

Modules


[PHP Modules] Core ctype curl date dom event fileinfo filter ftp hash iconv json libxml mbstring mongodb mysqlnd openssl pcre PDO pdo_sqlite Phar posix readline Reflection session SimpleXML sockets sodium SPL sqlite3 standard swoole tokenizer uuid xml xmlreader xmlwriter Zend OPcache zlib

[Zend Modules] Zend OPcache

The project installation is done using zend-expressive + zend-expressive-swoole via composer; Swoole HTTP server is run via CLI. Swoole compiled as follow:

    apt-get install -y --no-install-recommends \
    libhiredis0.13 libnghttp2-14 libhiredis-dev libnghttp2-dev && \
    cd /tmp && \
    git clone https://github.com/swoole/swoole-src.git swoole && \
    ( \
        cd swoole \
        && phpize \
        && ./configure --enable-sockets --enable-async-redis --enable-openssl --enable-http2 --enable-swoole \
        && make -j$(nproc) \
        && make install \
    ) && \
    docker-php-ext-install /tmp/swoole && \
    rm -r /tmp/swoole

The write model use is attached. SavePdfDocument.txt

If necessary I can upload an archive of the entire project code.

jmikola commented 6 years ago

The following error 'BulkWrite objects may only be executed once and this instance has already been executed' is generated on every post after the first one (the first does insert data correctly) working with Swoole HTTP server.

Looking at SavePdfDocument.txt, you're constructing a single BulkWrite instance in the constructor and each invocation of save() will add an insert operation to it (calling BulkWrite::insert()) and then execute the bulk write via Manager::executeBulkWrite(). If your application is calling save() more than once on a single SavePdfDocument object, then this exception is entirely expected.

Note the internal state of a BulkWrite is not reset after execution. Once a write operation is added it remains in the object for its lifetime.

While the exception message is quite clear, I realize that the MongoDB\Driver\BulkWrite and MongoDB\Driver\Manager::executeBulkWrite() don't actually note that BulkWrite instances can only be executed a single time. I've opened PHPC-1269 to track that improvement.

MassiAtZend commented 6 years ago

The 'save' method is called once per post, my the point is what happens with 'mshutdown' or 'rshutdown' when working with Swoole HTTP server? Every post should create a new instance of the BulkWrite, in fact, the problem does not occur when working with 'cli-server' SAPI.

jmikola commented 6 years ago

what happens with 'mshutdown' or 'rshutdown' when working with Swoole HTTP server?

This is better asked in one of the Swoole community channels. I don't have much experience with the framework of its internals.

I'm not sure how either the module or request shutdown handlers relate to this error. The BulkWrite object is a zval encapsulating a mongoc_bulk_operation_t struct. That struct may contain a pointer to mongoc_client_t, which we set and immediately use during the Manager::executeBulkWrite() method. After that point, the client pointer is never accessed (even when BulkWrite is freed). If a BulkWrite is used a second time with Manager::executeBulkWrite() we immediately throw based on a boolean flag in the BulkWrite zval. With regard to MSHUTDOWN, the driver does end up freeing persisted clients there, but that is entirely independent of BulkWrite, which simply disregards the client pointer after its initial execution during userland control flow.

One suggestion may be to check BulkWrite::count() when you encounter this exception in your save() method. If this returns something greater than 1, we can conclude that insert() was called multiple times on the same object.

Every post should create a new instance of the BulkWrite, in fact, the problem does not occur when working with 'cli-server' SAPI.

If this error cannot be reproduced in a CLI or standard web SAPI, I would suspect that something related to Swoole is sharing the object across multiple threads (not necessarily in the pthread sense) of execution; however, knowing if the operation count is >1 at the time an exception is thrown will justify that suspicion.

MassiAtZend commented 6 years ago

Thanks a lot for the thorough explanation, it is definitely due to some caching optimization the Swoole extension applies.

derickr commented 6 years ago

OK. Closing this out then. If you have further comments/questions regarding this subject, please feel free to reopen. Otherwise, please open a new ticket if it's unrelated.