laravel-mongodb icon indicating copy to clipboard operation
laravel-mongodb copied to clipboard

Can't store cache lock

Open VietTQ8 opened this issue 1 year ago • 5 comments

  • Laravel-mongodb Version: #.#.#
  • PHP Version: #.#.#
  • Database Driver & Version:

Description:

I have issue when store data to cache lock with MongoDB driver. error also occurs when dispatch a job implements ShouldBeUnique.

Method acquire Illuminate\Cache\DatabaseLock catch QueryException to update record cache lock table. But with mongodb driver connection throw MongoDB\Driver\Exception\BulkWriteException so can not excute snippet code in catch block

Illuminate\Cache\DatabaseLock

public function acquire()
    {
        $acquired = false;

        try {
            $this->connection->table($this->table)->insert([
                'key' => $this->name,
                'owner' => $this->owner,
                'expiration' => $this->expiresAt(),
            ]);

            $acquired = true;
        } catch (QueryException $e) {
            $updated = $this->connection->table($this->table)
                ->where('key', $this->name)
                ->where(function ($query) {
                    return $query->where('owner', $this->owner)->orWhere('expiration', '<=', time());
                })->update([
                    'owner' => $this->owner,
                    'expiration' => $this->expiresAt(),
                ]);

            $acquired = $updated >= 1;
        }

        if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) {
            $this->connection->table($this->table)->where('expiration', '<=', time())->delete();
        }

        return $acquired;
    }

Steps to reproduce

$lock = Cache::lock('foo', 10); $lock->block(5);

Job:

class ExampleJob implements ShouldQueue, ShouldBeUnique {
        use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UsesBackoffStrategy;

        public RequestQueue $request;
        public $tries = 2;

        /**
         * The number of seconds the job can run before timing out.
         *
         * @var int
         */
        public $timeout = 600;

        /**
         * Indicate if the job should be marked as failed on timeout.
         *
         * @var bool
         */
        public $failOnTimeout = true;

        /**
         * Create a new job instance.
         *
         * @return void
         */
        public function __construct() {
            //
        }

        /**
         * Execute the job.
         *
         * @return void
         */
        public function handle() {
       }

dispatch job: ExampleJob ::dispatch();

Logs: Exception message like:

local.ERROR: E11000 duplicate key error collection: mes.cache_locks index: key_1 dup key: { key: "_cachefoo" } {"userId":"admin","exception":"[object] (MongoDB\\Driver\\Exception\\BulkWriteException(code: 11000): E11000 duplicate key error collection: mes.cache_locks index: key_1 dup key: { key: \"_cachefoo\" } at /var/www/vendor/mongodb/mongodb/src/Operation/InsertMany.php:157)

Could you suggest any ways solve this issue ?

VietTQ8 avatar Sep 08 '23 10:09 VietTQ8

Thank you for reporting this! It looks like one way would be to only throw a QueryException from the MongoDB connection. However, this is not really desirable, as QueryException extends PDOException, which definitely isn't the right inheritance chain. Instead, we would either have to provide our own DatabaseStore for people to use with MongoDB, or teach the DatabaseLock class in Laravel itself that there may be other exceptions that needed to be caught (e.g. by providing a marker interface instead of a class to extend).

We'll take a look internally and discuss this further. Could you also please share your cache configuration so we have an idea how you configured the cache and can reproduce it in our own tests? Thank you!

alcaeus avatar Oct 19 '23 09:10 alcaeus

Thank you for reporting this! It looks like one way would be to only throw a QueryException from the MongoDB connection. However, this is not really desirable, as QueryException extends PDOException, which definitely isn't the right inheritance chain. Instead, we would either have to provide our own DatabaseStore for people to use with MongoDB, or teach the DatabaseLock class in Laravel itself that there may be other exceptions that needed to be caught (e.g. by providing a marker interface instead of a class to extend).

We'll take a look internally and discuss this further. Could you also please share your cache configuration so we have an idea how you configured the cache and can reproduce it in our own tests? Thank you!

Yes sure this is my cache config

<?php

use Illuminate\Support\Str;

return [

    /*
    |--------------------------------------------------------------------------
    | Default Cache Store
    |--------------------------------------------------------------------------
    |
    | This option controls the default cache connection that gets used while
    | using this caching library. This connection is used when another is
    | not explicitly specified when executing a given caching function.
    |
    | Supported: "apc", "array", "database", "file",
    |            "memcached", "redis", "dynamodb"
    |
    */

    'default' => env('CACHE_DRIVER', 'file'),

    /*
    |--------------------------------------------------------------------------
    | Cache Stores
    |--------------------------------------------------------------------------
    |
    | Here you may define all of the cache "stores" for your application as
    | well as their drivers. You may even define multiple stores for the
    | same cache driver to group types of items stored in your caches.
    |
    */

    'stores' => [

        'array' => [
            'driver' => 'array',
            'serialize' => false,
        ],

        'database' => [
            'driver' => 'database',
            'table' => 'cache',
            'connection' => null,
        ],

        'file' => [
            'driver' => 'file',
            'path' => storage_path('framework/cache/data'),
        ],

    ],


    'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),

];

in .env set CACHE_DRIVER=database

Hope we can find the way to solve it, @alcaeus . Thanks!

VietTQ8 avatar Oct 19 '23 16:10 VietTQ8

Ticked by PHPORM-99

GromNaN avatar Nov 08 '23 15:11 GromNaN

Ticked by PHPORM-99

Hi @GromNaN could you have any information about to resolve this ticket ?

VietTQ8 avatar Jan 17 '24 16:01 VietTQ8

The DatabaseLock can't be used. We need to create a dedicated MongoDBStore (cache) and MongoDBLock (lock), like there is for DynamoDB and Redis in the Laravel Framework.

Are you interested in working on this topic?

GromNaN avatar Jan 19 '24 09:01 GromNaN

The DatabaseLock can't be used. We need to create a dedicated MongoDBStore (cache) and MongoDBLock (lock), like there is for DynamoDB and Redis in the Laravel Framework.

Are you interested in working on this topic?

Yes thanks @GromNaN , I think it is the best solution. Could you detail about this topic?

VietTQ8 avatar Feb 01 '24 04:02 VietTQ8

How I think it should be implemented (but I never worked with Laravel lock and cache store before):

Cache

Lock

GromNaN avatar Feb 01 '24 11:02 GromNaN