colopl / laravel-spanner

Laravel database driver for Google Cloud Spanner
Apache License 2.0
96 stars 16 forks source link

Nested transactions are not supported by this client. #232

Open matthewjumpsoffbuildings opened 4 days ago

matthewjumpsoffbuildings commented 4 days ago

When I upgraded from v7 to v8 of this driver, I found that suddenly code that worked fine in v7 is now throwing the "Nested transactions are not supported by this client." error from google/cloud-spanner/src/Database.php:912

I know that in the ManagesTransactions trait there is the following block

 // Since Cloud Spanner does not support nested transactions,
        // we use Laravel's transaction management for nested transactions only.
        if ($this->transactions > 0) {
            return parent::transaction($callback, $attempts);
        }

Which I would assume is supposed to stop nested transaction calls from throwing the error I am getting, and in v7 it does seem to work, but something has changed in v8 that is making it no longer work?

I know its probably hard to debug without a demo repository, but I am hoping you have a some idea what might have changed with transaction handling from v7 to v8 that might be causing this

If it is helpful here is the error and call stack

 Nested transactions are not supported by this client. {"userId":"03faf8f5-0dde-4c65-bc4e-52f823b6e69e","exception":"[object] (BadMethodCallException(code: 0): Nested transactions are not supported by this client. at /vendor/google/cloud-spanner/src/Database.php:912)
[stacktrace]
#0 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(76): Google\\Cloud\\Spanner\\Database->runTransaction(Object(Closure), Array)
#1 /vendor/colopl/laravel-spanner/src/Connection.php(555): Colopl\\Spanner\\Connection->Colopl\\Spanner\\Concerns\\{closure}()
#2 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(75): Colopl\\Spanner\\Connection->withSessionNotFoundHandling(Object(Closure))
#3 /vendor/colopl/laravel-spanner/src/Connection.php(346): Colopl\\Spanner\\Connection->transaction(Object(Closure))
#4 /vendor/laravel/framework/src/Illuminate/Database/Connection.php(536): Colopl\\Spanner\\Connection->affectingStatement('update `media` ...', Array)
#5 /vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(3859): Illuminate\\Database\\Connection->update('update `media` ...', Array)
#6 /vendor/colopl/laravel-spanner/src/Query/Builder.php(63): Illuminate\\Database\\Query\\Builder->update(Object(Illuminate\\Support\\Collection))
#7 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(1115): Colopl\\Spanner\\Query\\Builder->update(Array)
#8 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1238): Illuminate\\Database\\Eloquent\\Builder->update(Array)
#9 /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1155): Illuminate\\Database\\Eloquent\\Model->performUpdate(Object(Illuminate\\Database\\Eloquent\\Builder))
#10 /vendor/spatie/laravel-medialibrary/src/MediaCollections/Models/Media.php(515): Illuminate\\Database\\Eloquent\\Model->save()
#11 /vendor/spatie/laravel-medialibrary/src/MediaCollections/Models/Media.php(293): Spatie\\MediaLibrary\\MediaCollections\\Models\\Media->saveOrTouch()
#12 /vendor/spatie/laravel-medialibrary/src/Conversions/Actions/PerformConversionAction.php(49): Spatie\\MediaLibrary\\MediaCollections\\Models\\Media->markAsConversionGenerated('thumb')
#13 /vendor/spatie/laravel-medialibrary/src/Conversions/FileManipulator.php(71): Spatie\\MediaLibrary\\Conversions\\Actions\\PerformConversionAction->execute(Object(Spatie\\MediaLibrary\\Conversions\\Conversion), Object(App\\Models\\Media), '/Users/matt/Dev...')
#14 /vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(257): Spatie\\MediaLibrary\\Conversions\\FileManipulator->Spatie\\MediaLibrary\\Conversions\\{closure}(Object(Spatie\\MediaLibrary\\Conversions\\Conversion), 0)
#15 /vendor/spatie/laravel-medialibrary/src/Conversions/FileManipulator.php(70): Illuminate\\Support\\Collection->each(Object(Closure))
#16 /vendor/spatie/laravel-medialibrary/src/Conversions/Jobs/PerformConversionsJob.php(29): Spatie\\MediaLibrary\\Conversions\\FileManipulator->performConversions(Object(Spatie\\MediaLibrary\\Conversions\\ConversionCollection), Object(App\\Models\\Media), false)
#17 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob->handle(Object(Spatie\\MediaLibrary\\Conversions\\FileManipulator))
#18 /vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#19 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(95): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#20 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#21 /vendor/laravel/framework/src/Illuminate/Container/Container.php(690): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#22 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(128): Illuminate\\Container\\Container->call(Array)
#23 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Bus\\Dispatcher->Illuminate\\Bus\\{closure}(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob))
#24 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob))
#25 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(132): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#26 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(124): Illuminate\\Bus\\Dispatcher->dispatchNow(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob), false)
#27 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Queue\\CallQueuedHandler->Illuminate\\Queue\\{closure}(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob))
#28 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob))
#29 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(123): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#30 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(71): Illuminate\\Queue\\CallQueuedHandler->dispatchThroughMiddleware(Object(Illuminate\\Queue\\Jobs\\SyncJob), Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob))
#31 /vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(102): Illuminate\\Queue\\CallQueuedHandler->call(Object(Illuminate\\Queue\\Jobs\\SyncJob), Array)
#32 /vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php(76): Illuminate\\Queue\\Jobs\\Job->fire()
#33 /vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php(52): Illuminate\\Queue\\SyncQueue->executeJob(Object(Spatie\\MediaLibrary\\Conversions\\Jobs\\PerformConversionsJob), '', '')
#34 /vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionRecord.php(69): Illuminate\\Queue\\SyncQueue->Illuminate\\Queue\\{closure}()
#35 /vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php(65): Illuminate\\Database\\DatabaseTransactionRecord->executeCallbacks()
#36 [internal function]: Illuminate\\Support\\HigherOrderCollectionProxy->Illuminate\\Support\\{closure}(Object(Illuminate\\Database\\DatabaseTransactionRecord), 1)
#37 /vendor/laravel/framework/src/Illuminate/Collections/Arr.php(605): array_map(Object(Closure), Array, Array)
#38 /vendor/laravel/framework/src/Illuminate/Collections/Collection.php(789): Illuminate\\Support\\Arr::map(Array, Object(Closure))
#39 /vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php(64): Illuminate\\Support\\Collection->map(Object(Closure))
#40 /vendor/laravel/framework/src/Illuminate/Database/DatabaseTransactionsManager.php(96): Illuminate\\Support\\HigherOrderCollectionProxy->__call('executeCallback...', Array)
#41 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(178): Illuminate\\Database\\DatabaseTransactionsManager->commit('spanner-emulato...', 1, 0)
#42 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(94): Colopl\\Spanner\\Connection->performSpannerCommit()
#43 [internal function]: Colopl\\Spanner\\Connection->Colopl\\Spanner\\Concerns\\{closure}(Object(Google\\Cloud\\Spanner\\Transaction))
#44 /vendor/google/cloud-spanner/src/Database.php(968): call_user_func(Object(Closure), Object(Google\\Cloud\\Spanner\\Transaction))
#45 [internal function]: Google\\Cloud\\Spanner\\Database->Google\\Cloud\\Spanner\\{closure}(Object(Closure), Object(Google\\Cloud\\Spanner\\Session\\Session), Array)
#46 /vendor/google/cloud-core/src/Retry.php(80): call_user_func_array(Object(Closure), Array)
#47 /vendor/google/cloud-spanner/src/Database.php(986): Google\\Cloud\\Core\\Retry->execute(Object(Closure), Array)
#48 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(76): Google\\Cloud\\Spanner\\Database->runTransaction(Object(Closure), Array)
#49 /vendor/colopl/laravel-spanner/src/Connection.php(555): Colopl\\Spanner\\Connection->Colopl\\Spanner\\Concerns\\{closure}()
#50 /vendor/colopl/laravel-spanner/src/Concerns/ManagesTransactions.php(75): Colopl\\Spanner\\Connection->withSessionNotFoundHandling(Object(Closure))
#51 /vendor/laravel/nova/src/Http/Controllers/ResourceUpdateController.php(37): Colopl\\Spanner\\Connection->transaction(Object(Closure))
taka-oyama commented 4 days ago

Can you at least narrow down to the exact version? thanks.

matthewjumpsoffbuildings commented 3 days ago

Sure, 7.4.2 worked fine, but 8.1.2 up to 8.2.0 did not. I didnt try 8.1.0-8.1.1 because the updateOrInsert signature wasnt compatible with the version of Laravel 11 I had, but I can also try and downgrade Laravel if you think that testing 8.1.0-8.1.1 would help narrow it down

taka-oyama commented 3 days ago

Can you try updating only google/cloud-spanner while using 7.4.2? Also, if that works, then please try updating to 8.0.0 and let me know if the error still occurs.

matthewjumpsoffbuildings commented 3 days ago

I used the latest google/cloud-spanner in both (1.86.0)

I will test 8.0.0 shortly and let you know, thanks

taka-oyama commented 3 days ago

As far as I can tell, I haven't changed anything transaction related https://github.com/colopl/laravel-spanner/compare/v7.4.2...v8.1.2. So I'm guessing there might be changes in related libraries.

matthewjumpsoffbuildings commented 3 days ago

I just tested 8.0.0 and the same issue occurs, "Nested transactions are not supported by this client" is thrown

taka-oyama commented 3 days ago

Could be something that changed on laravel's side. Can you try lowering laravel 11's version to 11.0.0?

matthewjumpsoffbuildings commented 3 days ago

Just tried Laravel 11.0.0 with version 8.0.0 of this driver, still throwing

taka-oyama commented 3 days ago

what was laravel/framework 's version when you were on laravel-spanner v7.4.2?

matthewjumpsoffbuildings commented 3 days ago

Heres something interesting - on my fork I removed the Laravel 11 requirement from composer.json and installed v8.2.0 in a Laravel 10 project, and it doesn't throw the error any more

So it looks like something has changed in Laravel between 10 and 11 that is making passing the nested transaction handling up the inheritance chain no longer work as expected?

matthewjumpsoffbuildings commented 3 days ago

what was laravel/framework 's version when you were on laravel-spanner v7.4.2?

I was on version 10.48.22

(pardon the edit, copy pasted the wrong value)

taka-oyama commented 3 days ago

At this point I really can't do much without reproducible code. I can tell from the logs that you have some sort of job running in SyncQueue. Is the job dispatched after a commit?

matthewjumpsoffbuildings commented 3 days ago

Yes, basically what is happening in that call stack is a model is being created via a Laravel Nova resource update request, but I also have the spatie/laravel-medialibrary package associated with said model, which I guess internally uses SyncQueue to commit to its own media table when editing models that have associated media.

I will see what I can do about creating a demo repo