Problem/Motivation
#2004370: Batch the configuration synchronisation process enabled the configuration import process to use the batch process. Unfortunately this broke the locking mechanism since Lock::releaseAll() will remove any locks at the end of the first batch request.
Proposed resolution
Create a lock.persistent
service. The current implementation extends database lock backend and makes it persistent by making the lock id a constant and not registering a shutdown function during construction. The lock must be able to exist across requests unlike the current database implementation.
Remaining tasks
Review patch
User interface changes
None
API changes
None - the lock
service in config importer is replaced with the new lock.persistent
service but they implement the same interface.
Comment | File | Size | Author |
---|---|---|---|
#75 | 2205527.75.patch | 11.82 KB | cilefen |
#75 | interdiff-71-75.txt | 774 bytes | cilefen |
#71 | 2205527.71.patch | 11.82 KB | alexpott |
#71 | 70-71-interdiff.txt | 1.1 KB | alexpott |
#70 | 2205527.70.patch | 11.64 KB | alexpott |
Comments
Comment #1
alexpottComment #2
pounardShouldn't this be "add an applicative lock API then use it" ?
Comment #3
catchI was pondering that as well, the implementation is pretty much exactly the same as the existing lock API, just without the end of request cleanup. Seems worth having - will be other cases where you want to lock for the duration of a batch.
Comment #4
pounardIt should probably have its own API (pretty much more or less the same as lock API) with some additional properties to the lock (owner identifier, group identifier, etc...) that can be used to provide multiple locks release based on owner (logout or session drop) and based on groups/token (use cases I had more than once).
Comment #5
catchComment #6
mikeytown2 CreditAttribution: mikeytown2 commentedCould we get away with passing in a custom value to DatabaseLockBackend::acquire & DatabaseLockBackend::release instead of using LockBackendAbstract::getLockId?
What ever we do decide to do, having a cron job that cleans up expired locks would be needed.
Above is essentially what I do in HTTPRL and works because I use the lock to verify that the new process should be able to run. It then removes the lock and continues on from there. Generalizing this workflow might take a new API that closely resembles the current locking api; really depends on how it is used.
Comment #7
Gaelan CreditAttribution: Gaelan commentedI'm gonna try this.
Comment #8
Gaelan CreditAttribution: Gaelan commentedHere's a start. It adds a PersistentLockBackend, but it doesn't do anything with it yet.
Comment #9
Gaelan CreditAttribution: Gaelan commentedUm.
Comment #10
Gaelan CreditAttribution: Gaelan commentedPart 2!
Comment #11
Gaelan CreditAttribution: Gaelan commentedComment #12
Gaelan CreditAttribution: Gaelan commentedAdd changes suggested by @alexpott. NOTE: the last interdiff wasn't an interdiff, so this interdiff goes back to #9.
Comment #16
Gaelan CreditAttribution: Gaelan commented/me is stumped.
Comment #17
pounardThis should not be called "persistent lock" because it's more a property than its definition, it should be called "applicative lock" or "user lock" instead.
Instead of using a key value store, using a custom implementation in there may be cleaner. Most methods of the key value store won't be used in here anyway, and a lock has really a few methods. Key value store don't really ensure atomicity in their method calls whereas a lock should, even if it's a business lock.
A lot of business is missing in there, there should ways to group locks altogether (locking multiple business objects and releasing all of them at once) as long as a few other properties (such as user identifier, which would allow to drop all locks of a user at once, on logout for example).
Comment #18
martin107 CreditAttribution: martin107 commentedComment #19
camprandall CreditAttribution: camprandall commentedI will re-roll this.
Comment #20
camprandall CreditAttribution: camprandall commentedI re-rolled the patch from comment 12.
Comment #22
Xen CreditAttribution: Xen commentedAs @pounard points out, KeyValueExpirable is unsuitable as a lock store, due to the lack of guaranteed atomicity.
But instead of reimplementing the wheel, we can just reuse the locking logic of DatabaseLockBackend by subclassing it and disabling the releasing of locks when the request finishes.
I've implemented that in this patch (no interdiff, as there's too little commonality).
It's still up for discussion whether it should be lock.persistent or something else, and whether the inheritance shouldn't be the other way around so DatabaseLockBackend is the persistent one and RequestDatabaseLockBackend extends it with clearing locks on request end (and consequently lock.request).
Comment #24
pounardI do think that having an applicative lock forces us to reinvent the wheel, you need a few other properties on those locks than only a name, or it's rather useless. For advanced locking I can see a lot of nice features to have:
For all of those uses cases, you need the applicative lock to be have and be queried using:
For this to be implemented, we indeed need to reinvent the wheel, but I think this is a good thing to have because it paves the way to great editing features. Think of a site where you attempt to edit a node and the UI tells you that some other folk's actually editing it, since when did he lock it, etc...
Comment #25
Xen CreditAttribution: Xen commentedSimple locks is not "rather useless", Drupal has been using them for years, and it's exactly what we need here. More advanced locks is all good, but it's not what we need here, and there's no reason to make simple locks not-simple, in order to support a, as yet, theoretical need. It's quite simple to add a new service for more advanced locks, which those that need it can then use.
Not quite sure how I managed to break my patch, but I need to take a look at the lockId thing again anyway.
Comment #26
martin107 CreditAttribution: martin107 commented@Xen
Added missing use statement.
Comment #28
pounardThe downside of doing things simple here would be that we'd potentially end-up with three different locking levels (and different API's):
- technical mutex (actual locking system)
- persistent mutex (this one)
- applicative lock (more complex and business oriented one)
So much for not reinventing the wheel and API consistency.
Actually if you want stuff to remain simple I'd suggest that instead of creating a new abstraction for locking just add a boolean status that is "persistent" to actual locking API. This would make things way simpler and won't need to add a new service. It would actually be much simpler to implement, the boolean status don't need to be in database, but just in the PHP thread memory since its only use would be to skip the lock release at request termination.
Doing that, and single and only problem remains: allow the current session to reacquire the same lock (for exemple within a batch), but all you have to do to allow this is to persist the lock id global into session or into the batch. Note that this might cause concurrency problem between two opened browser tabs using the same session, so all you need to do is to add a owner token column, and persist that into the batch.
For example:
Adding a new service, a new API, and making a distinction between those kind of mutexes sound like a useless code and API indirection level.
Comment #29
Xen CreditAttribution: Xen commented@pounard
> So much for not reinventing the wheel and API consistency.
'cuse me? We're talking one new service, with exactly the same API as the existing lock service. That's not exactly reinventing the wheel, especially as it's using the same code as the existing, but for two lines. It seems to me to be exactly this kind of thing that "services" were invented for.
And while I think your suggestion for an alternative implementation does have some merit, it *is* an API change, as you're extending the acquire method signature.
I think it will be hard to swallow for the bigwigs, as the third argument to the method *drastically* alters its behavior. An alternative would be acquirePersistent, but this changes the API even more, besides being ugly.
By all means, come up with a suggesting for how to implement an advanced locking system, I'm curious how you'll approach the problem. Seems to me that in order to add all kind of things to a lock, we'll have to have a lock object to work on. And a concept of "things that can be locked" instead of the "random string" we have now.
In the meantime, I'll try to get this patch passing. I have half a mind to rename the existing lock service lock.request and fix up ALL THE THINGS!!!
Comment #30
Xen CreditAttribution: Xen commented26: move_configuration-2205527-26.patch queued for re-testing.
Comment #31
pounardSorry I of course didn't meant to be rude, and I do respect your point of view here.
Nevertheless, I'm just suggesting that evolving the new API instead of having yet another service, it sounds odd to have to different locking services which serves almost the same purpose and use the same API.
Comment #32
Xen CreditAttribution: Xen commentedOK, was a bit overeager in applying lock.persistent. UnitTests shouldn't use it, as the default lock service gets replaced by the NullLockBackend, as there's no database.
The last failure I think is unrelated, as I'm pretty sure I've seen another issue for it.
I still think that it's basically wrong that the default lock is bound to the request, without it being explicit in the class/service name. I'd think that it would be a DrupalWTF for developers coming from elsewhere, already familiar with the lock concept. But that's the kind of change that needs a nod from Damien.
Comment #34
Xen CreditAttribution: Xen commented@pounard
Fair enought, I don't want to be rude either, I'm just coming from the other direction just trying fix the basic issue for the moment being.
Frankly, I don't have an idea as to how a new API should look, but I'm quite interested if someone comes up with an elegant solution. In this case, I'm more of a "fix, then improve" type of guy. As it is, the wishlist is a tad to abstract for me, I need something a bit more concrete before getting creative.
Comment #35
pounardNo worries, I had written such applicative locks so many times because Drupal core doesn't ship any that I try to make this happen. I agree this might not be the right issue thought. Nevertheless, I don't try to do D8 developement (anymore) because I quite don't like it much (that's personal nothing I really complain about here) but I still follow it as much as I can, and I see there are a lot, really a lot, of services in D8 core today, and by adding yet another one I'm afraid at some point, there'll be so many that module developers won't even know that a persistent lock exists.
Comment #37
Xen CreditAttribution: Xen commentedManaged to find that last failure with a bit of help from https://www.drupal.org/node/2239969
KernelTestBase needs to take the new lock service into account. Which is a bit ugly, but unless the locking service gets redesigned, it'll have to do.
Comment #38
cilefen CreditAttribution: cilefen commentedReroll.
Comment #39
cilefen CreditAttribution: cilefen commentedWe will add tests for PersistentDatabaseLockBackend in this issue.
Comment #40
cilefen CreditAttribution: cilefen commentedThis is a reroll based on a conflict caused by #2148199: Use snapshot to warn users if the configuration has changed since last import in ConfigSync.
Comment #41
alexpottAdded tests for the new persistent lock service - this looks good go.
There is no need to set up the service in the minimal container for KernelTestBase - if this is required for a KernelTestBase test then that test can simply override containerBuild.
Comment #43
cilefen CreditAttribution: cilefen commentedAdded the semaphore table to FieldImportDeleteUninstallTest.
Comment #45
cilefen CreditAttribution: cilefen commentedComment #46
cilefen CreditAttribution: cilefen commentedComment #47
pwolanin CreditAttribution: pwolanin commentedShould the persistent lock class have a different interface - at least by name? or an added method so you can confirm it's persistent?
Comment #48
alexpott#47 I don't think so. The lack of persistence of the usual lock service can not be detected :) - the clue is in the name. It certainly does not need a new interface.
Comment #49
alexpott@cilefen - great find on ConfigImporter::reinjectMe()
Comment #50
swentel CreditAttribution: swentel commentedLooks good to me, only one nitpick in the docs.
We use 'Contains' now.
Comment #51
martin107 CreditAttribution: martin107 commentedFixed.
Comment #52
swentel CreditAttribution: swentel commentedActually no, it's 'Contains nameOfFile' - so the 'of' is not needed :)
Comment #53
martin107 CreditAttribution: martin107 commentedAh yes conclusive proof that coding before breakfast is never a good idea, now matter how small the change.:)
PS also it needs a \ to be a full qualified name ...
Comment #54
cilefen CreditAttribution: cilefen commentedReroll.
Comment #55
Fabianx CreditAttribution: Fabianx commentedHm, from a code standpoint I would RTBC it, but that the backend is fixed to database feels a little strange.
Maybe we can inject the lock service instead with a special parameter ($persistent = TRUE / FALSE) and implement the interface and proxy the calls through?
I am putting it to RTBC to see what core committers think.
Comment #57
chx CreditAttribution: chx commentedComment #58
Fabianx CreditAttribution: Fabianx commentedComment #59
alexpottre #55 it's a bit tricky. We can't just proxy to the existing lock service since the shutdown function registered in the constructor and the fact we can't change the lock id in the same way. I think it is ok for this to be built by extending DatabaseLockBackend - if someone builds an alternative lock service then they can also build an alternative persistent lock service.
Comment #61
alexpottComment #64
alexpottWe should make the ConfigImporter less opinionated on what services it has injected.
Comment #65
Fabianx CreditAttribution: Fabianx commentedBack to RTBC!
Thanks for the feedback, if core committers agree, this is a good approach I am okay, too.
Comment #66
chx CreditAttribution: chx commentedbut that will not merely persist across multiple requests but multiple types of lock requesters as well.
Comment #67
alexpott@chx but we need the lock id to be a constant otherwise persistent locking will not work. Maybe you just want the comment updated? But I'm confused since doesn't the implementation of
LockBackendAbstract::getLockId()
also make the lock id persist across multiple types of lock requesters too?@Fabianx to make the persistent a proxy to the database lock service we need to do a lot of work on the database lock service. And even then it could not proxy directly to the service it would have to clone it since we need to have a different lock id for the persistent and non-persistent service.
Comment #68
Fabianx CreditAttribution: Fabianx commentedI got confused about the persistent, too.
If I understand this correctly, this is just a "prefix" that is appended / prepended to the actual lock ID, so its unique per request or persistent like here.
But I might be wrong, it definitely is confusing ...
Comment #69
catchAgreed on the feedback above - I'm not clear what this is for, given
lock ID being a method argument here.
Comment #70
alexpottre #69:
1. This is a confusion between lock ID and lock name - lock ID is badly named but this documentation is revealing.
2. Here the
$config_importer::LOCK_ID
is the lock name. Happy to change this constant to lock name to remove some confusion.Comment #71
alexpottAnd lets improve the docs to be more explicit and helpful.
Comment #72
cilefen CreditAttribution: cilefen commentedComment #73
catchBoth interdiffs look great. I'm also fine with the service database dependency in #62 the use case for swapping this out is considerably less common than the lock system in general, and it's still possible to do if you want.
Comment #74
Fabianx CreditAttribution: Fabianx commented> + // multiple requests. The lock ID is use as a page token to relate all the
nit - typo: use -> used
Besides that:
- Back to RTBC :).
Comment #75
cilefen CreditAttribution: cilefen commentedComment #76
chx CreditAttribution: chx commentedThanks.
Comment #77
catchCommitted/pushed to 8.0.x, thanks!
Comment #80
Wim LeersA critical bug in this functionality was found: #3204073: ConfigImporter acquires a lock for 30 seconds but this might not be long enough.