The lock component have saved me so many times. It helps me with race conditions,
it makes my code simpler and my application more reliable. I’m using it to fix all
kinds of problems and I’ve noticed that I use a few different methods. This article will
try to explain these methods or “strategies”. My hope is to make life simpler for everybody else to
implement Symfony Lock component with Messenger.
No two messages handled at the same time
This strategy is the simplest one and is good if you want something that “works”.
It is not the most efficient one and it required a predefined list of messages.
The idea is that two message of the same class cannot be handled at the same time.
One needs to wait for the other to finish.
The handler for CreateOrganization and StorePerformanceData will create an entity
if none exists. That is why I need a lock or the second message will complain that
an object with id X already exists.
The message holds the resource ID
This strategy is a bit more flexible. It introduces an interface that is added
to the messages. We can now handle two messages from the same class at the same time
as long as they have different keys. The key should contain some ID to avoid race
conditions.
A potential drawback of this is that a message cannot configure auto release timeout
or if it should be blocking wait or not.
Configurable Locks
To build on top of the previous strategy, one can create a LockConfig class
that holds instructions for the LockMiddleware. This strategy is more generic
and may be good for complex applications or as a reusable package.
Note the use if RecoverableMessageHandlingException. If we throw an instance
of RecoverableExceptionInterface, then the message will not go to the failure
queue after 3 failed tries to handle the message.
Lock acquired by the handler
Sometimes you want all the logic in the handler, but you also want to make sure
the lock is released after the database transaction is committed, ie released
after DoctrineTransactionMiddleware.
This can be done by telling the middleware: “If you see that this message has been
processed, please release the lock”.
Bonus topic
I discovered a hidden gem in the Lock component when I needed to acquire a
Lock in a controller and then release it in a background process. You can serialize
the Key to the Lock. Normally, the Key is automatically created in the
LockFactory, but you can create the Key yourself.
Finally
Now when I forced you to read plenty of code examples, you may wonder why there isn’t
a single solution that always work. Different applications may have different requirements
and doing complicated/flexible solution should only be valid on complex applications
or reusable code.
I’m currently using all of these strategies in different applications. One might
argue that applications should be consistent with each other within the same company.
And yes, that is true. I just wish I read a blog post like this before I started
implementing my solution.
Also a big shout out to Jérémy Derussé who created
the Lock component and helped me with issues I had over the past year.
The Symfony Runtime component is AWESOME. I did a talk about it at
Symfony World 2021 where I explain how and
why it works. I spent a lot of time on the cont...
I’ve been blogging now and then for over a decade now. I have used Drupal, Wordpress,
plain HTML files, static generators as Jekyll and Sculpin. I’ve also us...
It has been one year since Jérémy Derussé and I started to work on a new API client
for AWS. At the time we could never have imagined how popular it would be...
Symfony Messenger is great. It has so many features and is super flexible. I want to
highlight one of these features in this small post: Failures and retrie...
Leave a Comment