Problem/Motivation
The issue is fairly straight forward...
Whenever people need to update D8 instructions are the following:
https://www.drupal.org/docs/8/update/update-core-via-composer
... Stuff
Site into maintenance.
... Stuff
Site out of maintenance.
In decoupled context there is no correct way to handle the event -
site is in maintenance mode
.
So there will be public clients hitting the website, as well as jsonapi endpoints fetching data.
The issue with the maintenance mode is easily reproducible:
1. Have public read access against jsonapi endpoints.
2. Enable site maintenance mode.
3. You get a non-valid jsonapi response and broken clients. Note that from the headers of the response clients can not distinguish that it's a maintenance mode response or some reverse proxy interrupted the request.
This is preventing safe deploys on systems running jsonapi or other modules used in decoupled context. In production environment when the system is a fully decoupled one, once the site is in maintenance mode - all public clients are down.
Proposed resolution
JSON:API should add it's own maintenance mode middle-ware before the core's existing one.
Remaining tasks
RTBC and commit
User interface changes
None.
API changes
jsonapi has a new setting to set Retry-After header values when the site is in maintenance mode.
Data model changes
None.
Release notes snippet
Maintenance mode now triggers an event to allow custom behaviour. JSON:API uses this event to serve a JSON:API response with Retry-After header when the site is in maintenance mode.
| Comment | File | Size | Author |
|---|---|---|---|
| #125 | interdiff_123-125.txt | 674 bytes | danflanagan8 |
| #125 | 10.0.x-3049048-125.patch | 24.9 KB | danflanagan8 |
| #123 | 10.0.x-3049048-123.patch | 24.95 KB | danflanagan8 |
| #118 | 3049048-118.patch | 24.88 KB | danflanagan8 |
| #118 | interdiff_113-118.txt | 3.72 KB | danflanagan8 |
Comments
Comment #2
wim leersNice catch! Would you mind adding a regression test to
\Drupal\Tests\jsonapi\Functional\JsonApiRegressionTest? :)Comment #3
ndobromirov commentedComment #4
ndobromirov commentedNo time for a patch this is the test PoC (not tested):
Comment #5
ndobromirov commentedAny hints for the direction to push this to?
I am leaning to option 1 - JSON:API specific middle-ware.
Comment #6
gabesullice@ndobromirov, that sounds right to me.
@Wim Leers, do you know if REST handles this at all?
Comment #7
wim leersGood question.
\Drupal\Core\EventSubscriber\MaintenanceModeSubscriberwas updated by #2653318: While in maintenance mode, REST routes respond with HTML instead of XML/JSON/… to provide a custom error response for non-HTML requests when maintenance mode is on. I bet this is the result that @ndobromirov is describing.JSON:API could add an event subscriber that runs earlier and does return a valid JSON:API response, albeit still an error response.
Comment #8
ndobromirov commentedAnother option that I can think of that just popped when I was reading the thread in #2653318: While in maintenance mode, REST routes respond with HTML instead of XML/JSON/… was to have a header that will have the message as well. So whenever there is a maintenance mode ON, there could be a header that is something like:
At that point browsers will show the body, as they currently do and any REST clients can use the header for the same purpose.
This might be problematic, as it's often the case that people put HTML in the maintenance mode to style the thing when people see it.
We might need to have the message placed a second time (with the new designation) or we just strip any HTML out of it.
Off-topic but this sounds nice as well: https://httpstatuses.com/503 to have a
Retry-Afterheader pushed. This could be a new setting on the maintenance page.Comment #9
ndobromirov commentedShould we move that against core now?
Comment #10
ndobromirov commentedMoved to core's issue queue, as that's the place to fix it.
Comment #11
ndobromirov commentedBased on all the things described in #2653318: While in maintenance mode, REST routes respond with HTML instead of XML/JSON/…, the main issue is still there - there is no consistent way to know that you are in maintenance mode compared to the site being too busy to respond and timed out on some proxy somewhere.
The best solution so far that I can see is the new header.
In the core's maintenance mode middle-ware all the needed bits are already there.
We just need to add the new missing header to the response.
Will post a PoC patch shortly for further discussions.
Comment #12
ndobromirov commentedComment #13
ndobromirov commentedComment #14
gabesullice@ndobromirov, I like the
retry-afteridea!I think we need to return a JSON:API compliant response for any JSON response. That could looks like this:
I don't really think a new header is necessary TBH.
Regarding the proposed solution, I think option #2 is best as well. JSON:API is a stable module in Drupal core and Drupal is API-first, so why shouldn't the core event subscriber have a special case for returning a JSON:API compliant response? It seems more maintainable to do that than to fuss with a new event subscriber and service priorities.
Comment #15
ndobromirov commentedNote that the header solution is generic and payload format agnostic. It will work for core's REST, GraphQL as well as for JSON:API transparently, as it's a solution on HTTP level.
Comment #16
gabesulliceWhat's the practical advantage of it being format agnostic? I think all real world HTTP clients expect one format or another. As for JSON:API, its spec says:
Unless we send no response body at all, we'll still be in clear violation of the spec. IOW, the current
text/plainresponse breaks clients and makes maintenance mode harder to debug, because all you see in the browser console is a JSON parse error.I'm not really opposed to the header, I like it actually, I just don't think it's a standalone solution.
Comment #17
ndobromirov commentedI am all for a dedicated JSON:API error response.
I was just trying to have minimal intervention in the middle-ware, that would show the idea.
I will update this one with a case for JSON:API as well, based on the format proposed in #14.
Comment #18
ndobromirov commentedHere is a revised patch to have a special response for JSON:API module.
Small tweaks here and there + a diff to #12.
Comment #19
wim leersThanks! 👍🙏
Unfortunately, this patch is adding awareness of JSON:API to Drupal core. We need to add an event subscriber to JSON:API itself; JSON:API needs to remain self-contained. Let's add a
Drupal\jsonapi\EventSubscriber\MaintenanceModeSubscriberinstead.Comment #20
ndobromirov commented@Wim, I've started in that direction initially, but this is problematic, as there is the core subscriber, as well a user module's subscriber that have priorities 30 and 31. JSON:API's one should get somewhere between them...
I have not checked what is on 29 and 32, but we will have to tweak this priorities at least and check how much we can move them around to be able to put that into a separate subscriber. Maybe we will need to have some more space for subscribers, so other modules could react on such events as well.
Comment #21
ndobromirov commentedHere is the subscriber version.
I've tweaked the subscriber priorities as I saw fit to actually trigger the middle-ware at the correct time - after the auto-log-out one and before the core's one. That will need thorough review thought.
To spare services injections and other bits, I am extending the core's maintenance mode subscriber.
No interdiff, as it's a completely different implementation.
Keeping tag needs tests...
Comment #22
wim leers#20: Why can't JSON:API's maintenance mode subscriber run with a higher priority?
\Drupal\user\EventSubscriber\MaintenanceModeSubscriberonly really makes sense for HTML responses.#21: We should avoid changing existing priorities if at all possible, otherwise we risk breaking BC. :(
Comment #24
ndobromirov commentedIt is not stating it explicitly. There is no
if ($request->getRequestFormat() === 'html') { # ..., as in the core's middleware to hande a concrete case.Whenever there are logged in requests, accounts with insufficient permissions should be logged out / shown the maintenance mode.
At least I am understating it that way.
I think you are right. New patch will resolve that.
- There are priority levels reverted.
- JSON:API middle-ware is moved to 35, so others can fit in before it if needed in future.
Comment #26
wim leersThis line is failing,
getRouteObject()is returningNULL. You probably want to look at how we do this elsewhere :)Comment #27
ndobromirov commentedHere is a revised patch for this :).
I've taken the check from other subscribers in the module.
Comment #28
ndobromirov commentedAdding a special tag :D
Comment #29
unstatu commentedTested. It works.
As a comment: if you browse the JSON API URL (e.g. http://localhost/jsonapi/user/user) with the web browser, it returns the plain maintenance message without the JSON formatting. I guess is not a problem since the browser is accepting HTML as response, but just to let you know.
Comment #30
wim leersThanks for pushing this forward! 🥳
This needs to be cleaned up.
We must document why we use these particular priorities.
See
\Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber::getSubscribedEvents()for an example.Missing trailing
}.Incomplete :)
Ehm …
TRUE && $somethingis the same as$something, so we can remove this :)This would need to be injected.
But the existing maintenance mode subscriber in core also doesn't do that. Perhaps for performance reasons? Let's try to find the answer in the last commit that touched it and let's document it properly.
This does not need to be translated!
@gabesullice could you check whether this should use
new ErrorCollection(…)?Comment #31
gabesullice#30.8. It should... unless for some reason the `ResourceResponseSubscriber` that JSON:API provides wouldn't get called after this phase of the request.
Comment #32
ndobromirov commentedShould manage to find time for this this week.
Comment #34
mglaman#30.6 For the kill switch, I don't know if there is any reason it wasn't injected per #2453351: Maintenance mode message ends up in page cache, served endlessly.
#30.8 \Drupal\jsonapi\JsonApiResource\ErrorCollection receives an array of \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface. We could construct an HttpException with 503 and pass it to error collection. But the question in #31 remains if ResourceResponseSubscriber would be called (which is why we need tests.)
I did notice the returned error object did not contain an array of errors, per https://jsonapi.org/examples/#error-objects.
It still needs a test.
Comment #38
bbralaGoing through old issues, this is a nice addition and does seem close to done.
#30.6 @wimleers I think this was an oversight, the bug was critical. I remember pretty well when we also notices that bug :x The commit introducing this only contains that line and a test. I think a task issue to move the call to injection is in place.
The current patch doesn't work when I test it, seems the
getRequestFormatalsways returns html. Digging a little deeper is seems routes are only set later, priority needs to be 31 max. I think this might have something to do with:KernelEvents::REQUEST => [['onKernelRequest', 32]],in the\Symfony\Component\HttpKernel\EventListener\RouterListenersince the metadata only gets added to the request after that event, which in turn allows us to find out the request wants to receiveapi_json.So, lets get the review in ^^
Comment #39
mvonfrie commentedI just found this issue because I have the same problem with my decoupled site. But I'm not using JSON:API as the requirements are kinda complex and very difficult to implement with JSON:API without producing tons of overhead on both server and client side. Instead I'm using custom controller actions. In other scenarios where you might have just a few simple REST endpoints (like providing a health check endpoint with some structured data – i. e. including if Drupal can connect to the database – consumable by uptime monitoring services) using JSON:API might be overkill.
To summarize, the same handling as for JSON:API request should work for "normal" JSON requests as well, that means when the request
Content-Typeheader has the valueapplication/jsonand not only when it hasapplication/vnd.api+json. This should returnThe same problem exists when the global exception handler kicks in and returns an error response (mainly HTTP 4xx, HTTP 500, HTTP 501 and HTTP 502 as they can occur before your controller action gets executed, which of course should implement error handling and return proper responses), both for JSON and JSON:API requests.
Comment #40
danflanagan8Here's a shot at test coverage for jsonapi in maintenance mode.
The patch in #34 doesn't cause this to pass, by the way. That's because this condition is not met:
Seems to me *any* request to a jsonapi should return json, but this is pretty far out of my comfort zone.UPDATE: Since I originally posted, I read #38 more carefully and confirmed that changing
35to32causes the new test case to pass.Comment #42
danflanagan8Ok, here's a patch that updates #34 with the suggestions from #38. The updated comments may be inaccurate, so review them carefully! This also has the test from #40. That test still needs review too.
In my manual testing, the maintenance mode message looks nice!
Comment #43
bbralaThanks for the help. I've gone through the code and have a few comments.
I don't think we need striptags here? If i check for the text/plain response in
core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.phpline 117 the response is not stripped.$response = new Response($this->getSiteMaintenanceMessage(), 503, ['Content-Type' => 'text/plain']);Why should we do it here then?
I dont really like 0.1 here, perhaps just put it in the bottom of this list of tests.
Also, when i look at the tests that look for example at the 404 (line 319) is also checks for
$this->assertSession()->responseHeaderContains('Content-Type', 'application/vnd.api+json');. I think that makes sense here also as a check.Comment #44
danflanagan8Thanks for the review, @bbrala.
I've made the requested adjustments, which I think are both good ideas. I'm happy to adjust the placement of the new test case again if you don't like where it ended up.
Regarding the
strip_tags("Why should we do it here then?"), that was a copy/paste job from comments in #38. It seems like we're probably fine matching the behavior ofcore/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriberComment #45
bbralaThanks for the changes, I think this is all fine like this. Did some extra manual testing and seems to work like a charm.
Comment #46
bbralaWent through the issue and added the correct credits.
Comment #47
mglaman🥳 way to go everyone, #44 looks great
Comment #49
danflanagan8That was a random test fail related to this issue: #3208791: [random test failure] Random fail in LayoutBuilderDisableInteractionsTest
Setting back to RTBC. Thanks all!
Comment #50
wim leersSorry, I have a few remarks 😅
This needs
added to the docblock, just like all other JSON:API event subscribers.
Retry-After?That'd allow API clients to retry automatically after N seconds.
What if we ask it to retry in 60 seconds? 🤓
$http_exception🙈Shouldn't we assert the message?
Comment #51
bbralaComment #52
wim leersRE:
Retry-After: I just said 1 minute because maintenance mode tends to be on relatively long. But +1 to make it as short as possible. How about5 + rand(0, 5)(i.e. between 5 and 10 seconds, to reduce the thundering herd effect).Comment #53
bbralaThat seems like it's reasonable. Then clients might actually not even notice that much:)
Comment #54
danflanagan8@Wim Leers and @bbrala, all good ideas. I felt tester's remorse for not asserting the message, so I'm glad that got called out actually.
Here's an update that I think checks all the boxes. Let me know if I missed or misunderstood anything.
Comment #56
danflanagan8It's that dern
LayoutBuilderDisableInteractionsTestagain. Setting back to Needs Review.Comment #57
bbralaReviewed, all feedback has been addressed, thanks!
Comment #58
alexpottReading this I keep on thinking well why is this not 31 - the answer is because \Drupal\user\EventSubscriber\MaintenanceModeSubscriber is on 31 and this needs to come before that. Which begs a question about logging out. The user module subscriber will log out accounts that aren't exempt but with this change now jsonapi will not. Are we sure we want this behaviour change? This behaviour was added in #363580: OpenID login fails when in maintenance mode. I've skimmed the issue and it's not 100% obvious as to why but one reason that makes some sense if you hit the maintenance mode with a user that is not exempt then logging out gives you a chance to log in as a user that is exempt if you have access to such an account.
Comment #59
alexpottIt feels as though this should be configurable in some way. People often know how long a site will be out for for maintenance and 5 seconds might be woefully short.
A couple of thoughts: we could make it a setting that people can configure in settings.php, we could add it to the jsonapi.settings configuration or we could add it as class property so at least if a site wants to decorate and extend this event listener it wouldn't have to change much.
I think adding it to the jsonapi.settings is probably the best place to do it. I think we don't need to add a UI here.
Comment #60
bbrala#58
You have a point about the logging out. But the fact is that it is not possible to login using jsonapi so any logging in would go though another channel.
Thinking about it, I think in this situation where you would be consuming this API though an application you might rather not be logged out. Combined with the retry-after this would mean that the whole process has a little impact as possible since the clients would reconnect after a while.
The only issue might be if there are changes deployed while in maintenance mode which need a relogin to work as they should.
What would you recon, should we move the listnener to make sure people are logged out? I really think this might be a bad experience in decoupled situations.
#59:
That makes sense. Adding a config variable for this is probably best. I added the "Needs change record" tag since we would need a change record for this change and the new setting i think.
Comment #61
alexpottI think the whole logging out if you hit the site and it is maintenance mode is a little odd. That's why I was trying to find out why it was added. I'm okay with not logging out but I think we need to document this difference in the code and explain the priority of 32 a little better in the code. The priorities feel very very fragile here fwiw. I certainly don't think we should move the listener to after the user one. I do wonder if we should make the user one html only since it is doing a redirect. And then the priority of the new listener could be 31 as well.
If this is the case then it is up to the update to take an action that would result in all users being logged out.
Comment #62
bbralaSo that would mean the following tasks:
$request->getRequestFormat() == 'html')toDrupal\user\EventSubscriber\MaintenanceModeSubscriber.JsonapiMaintenanceModeSubscriberto 31 and expand the documentation in the code to describe why we won't logout as done in theDrupal\user\EventSubscriber\MaintenanceModeSubscriber.jsonapi.settings.maintenance_header_retry_after.minandjsonapi.settings.maintenance_header_retry_after.maxto allow control over this setting by implementations.Change the code to read something like:
hook_update_Nto migrate the new configuration value. Default: min 5, max 10.Comment #63
danflanagan8Here's a patch that I think includes all of the requested changes.
I'm not sure what the N for the update_N is supposed to be. I made my best guess. I included a new test for the update path. I copied off of the one in the
actionmodule.I added fields for the new min/max values to the settings form with a little validation. Do we need to add tests for the Form at this point? I couldn't find tests for the existing form, which was pretty darn simple.
Comment #64
bbralaThanks for the patch!
N would be the update hook number. I can't tell you right now what that should be.
In the task list I mentioned not adding something to the interface, this is an advanced setting that doesn't really need a way to adjust in the admin interface. That also skips the need for some other core gates I think so I would prefer it without the formelements.
I've not looked at the code, I'm on mobile ATM. :)
Comment #65
danflanagan8You did indeed! (#62.3) I missed that apparently. No big deal. I'll undo the changes to the form in the next patch. I'm sure there will be other changes needed to the one in #63. That's a big interdiff to have everything else be perfect!
Comment #66
danflanagan8Here's a new patch without changes to the form. I'm posting an interdiff between this one and #54. Let's forget that #63 ever existed.
Comment #67
bbralaI've gone through the code. Also i've tested this locally and it works before the update although there is no config, and after the update the config is correct. When i change the values and import the config, the values are also updates on the header. Yay.
I'd like a @see here to this issue for clarification.
I would name this 9401 as that seems to be the consensus when i look through git history of earlier update hooks.
Cool to see this, haven't come across this yet before :)
Legacy? Why would this be legacy? If a look at a lot of the updatepatchtests it seems there is no real consensus on how they are tagged unless i miss something.
I would say, remove the legacy tag.
Dont forget to change the ID here also.
Only thing I was not 100% sure about is if this should be a hook_update or hook_post_update, but it seems config changes are fine in hook_update from what I can tell.
Comment #68
danflanagan8In the
UpdatePathTestBaseclass the docblock readsEnsure that the test is in the legacy group..But searching the codebase I see what you see: hugely inconsistent tagging. I would say
Updateis the most common tag to have.That docblock comment was added in this issue, though it doesn't give any context. That issue spun off of another one that features this comment which is the best I can find.
Is it ok if I leave it?
Comment #69
bbralaOk, keep legacy and jsonapi then, we'll see when we get to RTBC what is preferred. :)
Comment #70
danflanagan8Updates attached! Just changing 9400 to 9401 and adding the @see.
Comment #71
bbralaI've written a change record.
Comment #72
danflanagan8Thanks, @bbrala! I think it looks good. I added a couple notes and fixed a typo or two.
Comment #73
bbralaAs tests also pass, code looks good, handing of as RTBC again :) Thanks for the work @danflanagan8!
Comment #74
alexpottThe reason this is marked legacy is that update tests are prone to triggering deprecated code paths (by their very nature) and this prevents the test from failing for the reason because it is okay if they do trigger deprecated code.
There is a update path test that is not marked legacy to ensure that our update process is not triggering legacy in and of itself.
Comment #75
alexpottSince we have no UI I think it would be good if the config key name had seconds in it so people know the unit.
How about: maintenance_header_retry_seconds
This needs to be $config->save(TRUE) because don't want to recalculation all config schema at this point and we can guarantee that these types are correct.
Comment #76
danflanagan8Here are those few changes, @alexpott. I'll also update the change record to reflect the updated name for the config key.
Comment #77
bbralaYou actually mentioned that, oops forgot it in the task list.
Tests are green, changes seem to have addressed all feedback.
Comment #78
bbralaGood to know. But it seems there is a lot of update tests without that tag :)
Comment #79
alexpottLet's not add the default values here. Once the update has run these values will never be null. And a site is allowed to be "broken" before all of the hook_update_N() have been run.
Comment #80
danflanagan8Unless someone runs a config import before running a config export. I know that's developer error but it's easy enough to make that mistake.(I'm pretty sure I've done it!) I tested what happens if a developer were to make this mistake. The code>Retry-After value ends up being zero, and there's a php warning in the logs:
Warning: Trying to access array offset on value of type null in Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriber->onKernelRequestMaintenance() (line 66 of /var/www/html/core/modules/jsonapi/src/EventSubscriber/JsonapiMaintenanceModeSubscriber.php)If we remove the default values from the code, I think we should add an error to
jsonapi_requirementsif the new config values aren't set. Seeing as the jsonapi settings form does not expose these settings, this might all be especially confusing without a message.I'm also wondering if the
@seeinjsonapi_update_9401would be improved by pointing to the change record instead of this issue. Thoughts anyone?I'll wait to make changes until I get a response. I'm setting the status to NR to indicate that.
Comment #81
mglamanI think pointing to the change record is the norm, and makes it easier to understand why the upgrade is running.
Drupal core doesn't do this right now. So I think it's fine allowing a warning to be present if the upgrade wasn't run / or config was lost.
Comment #82
danflanagan8Thanks, @mglaman. Can I get some clarification on this comment:
Are you in favor of adding to
jsonapi_requirements? Or are you opposed to adding a new check tojsonapi_requirements?Comment #83
mglamanI'm not in favor of adding to
jsonapi_requirements. If we did, we'd have to bikeshed what the statement should be and how to best communicate. I see this issue holding up at least another month over that. And we can fallback on the fact Drupal core (for better or worse) hasn't handled this before as "we don't need to." And it is considered, in Drupal, the site is allowed to be partially broken between code deployment and updates finished.Comment #84
danflanagan8Thanks again, @mglaman!
Here's an updated patch that:
1. Removes the default values from code (see #74)
2. Changes the
@seeto point at the change record instead of this issue.I also touched up the IS so that the alternative proposed resolution has been removed.
Comment #85
alexpott@danflanagan8 there's way way more that can be broken if you don't manage your config through an update properly. This is one of the things the drush deploy command tries to help with.
Comment #86
alexpottThe @see should either be removed or point to the CR. It's doesn't need to point to the issue.
Comment #87
bbralaHmm, @alexpott
The change record was already added, see linked change record. The patch @danflanagan8 provided removed the default value as requested and updates the @see to the changerecord?
Not sure why you've hidden those in comment #85
Comment #88
danflanagan8The changes I made to the IS in #84 got reverted too. I think there was some kind of accident. :)
Comment #89
bbralaAs feedback was fixed, setting rtbc
Comment #90
bbralaHmm, think you might be right there @danflanagan8
Comment #91
alexpottYep I had the issue open too long. Sorry and thanks.
Comment #92
bbralaComment #93
bbralaComment #94
rar9 commentedHow to grab the drupal maintance text with graphQL to be displayed ?
and there must be a rule to only use this page id Drupal is actually in maintance?
#42 guide is all hard coded :-(
Comment #95
mglamanThis seems like a problem the GraphQL contrib would also need to solve on its own.
Comment #96
rar9 commentedBut GraphQL is just using json Data that Drupal hasto supply, for building react frontend
Comment #97
alexpottI was about to commit this when I realised that this issue contains quite a big behaviour change that we shouldn't do.
Doing some more thinking about this bit. I'm not sure we should make this change like this.
I think that we should consider making the current behaviour the default behaviour but making it overridable. I.e this event listener needs to trigger another event that the json api module listens for and handles. And the user module also needs to listen to and do the usual logout. The json api one would call stopPropagation() on the event it listens to if the request is has api_json as the format.
This would mean that we don't break anyone that is currently relying on this behaviour but also that we can fix this cleanly for json api in a way that the whole 30 - 32 event priority is not so important.
The pseudo code for this would look like this...
Also this particular event listener would not be in the user module - it should be in system or core. FYI I'm not sure about SystemEvents - but it needs to be something like that and the system module does provide the maintenance mode state stuff. But OTOH core provides the maintenance mode config.
Also adding a new event will make it much simpler for modules like GraphQL to do their own thing.
Comment #99
danflanagan8I *think* I understand what @alexpott is saying in #97. Here's a patch that tries to check all those boxes.
The big change here is that there is a new event called
MaintenanceModeEvents::MAINTENANCE_MODE_REQUESTthat is dispatched byDrupal\Core\EventSubscriber\MaintenanceModeSubscriberwhen the site is in maintenance mode and the user is not exempt. That new event can get listened to by any module. In the patch here,json_apilistens first, thenuser. If the event is still propagating thenDrupal\Core\EventSubscriber\MaintenanceModeSubscriberwill handle it.It's a huge patch so there's a lot to look at, but I think in the end it simplifies everything. For example, that mysterious call to
\Drupal::service('page_cache_kill_switch')->trigger();is only in one place now.Note that I added params to a few services. I did it in a BC way but didn't take the time to trigger deprecations at this point because I was lazy. Might as well get feedback first.
Comment #100
danflanagan8cs violation fix
Comment #101
bbralaSince we are adding a new argument, this should normally also include a deprecated message. See for example
Drupal\jsonapi\Normalizer\LinkCollectionNormalizer::__construct.Another extra argument, that should probably also include the deprecation message.
Should this also
$event->stopPropagation();as mentioned by @alexpott. This would stop the event going through and then rendering html.Comment #102
alexpottI think this should be a really low priority.
And yes if a maintenance mode subscriber sets a response it should stop propagation.
Comment #103
bbralaAfter discussion on slack with alexpott, removed the 4th point in my review about seperating the logic into another place.
Slack thread:
Comment #104
danflanagan8The
setResponsefunction automatically callsstopPropagation. I've added comments to make that clear.The rest of the changes in this patch (and there are many) are related to deprecations. As pointed out in 101.1 and 101.2 I needed to add some notices for new arguments.
But then I started thinking about how
user\EventSubscriber\MaintenanceModeSubscriberwas kind of a mess. First, it no longer used themaintenance_modeservice that was being injected. Second, I had renamed the public function since it was subscribing to a new event, which is kinda a BC no-no. It seemed like maybe the better way to go would be to deprecateuser\EventSubscriber\MaintenanceModeSubscriberand the corresponding service, and add a new class/service that does the right thing.So that's what I did. Hopefully it's not a mess.
The only thing left that I don't like is that the core
maintenance_mode_subscriberis being injected withconfig.factory, which is no longer needs. I don't know how big a deal that is.The interdiff might be kind of noisy.
Comment #105
bbralaTo be honest, i these changes are looking good. There are a few questions in regard to how we should go about deprecations and such. But rather have a framework manager have a look how we should proceed.
So i'm setting it to rtbc and adding `Needs framework manager review` tag.
Comment #106
alexpottDidn't know that setting the response stops propagation... that makes sense and makes this change look really neat. Nice.
I think we should change the existing maintenance mode subscriber rather then introducing a new one. Event subscribers are not part of the BC promise. We could deprecate the old kernel request method and introduce a new method but I think emptying getSubscribersEvents() and having a deprecated service is more disruptive than just making thing work the way we want them too. Other than this I really like what we've achieved here.
Comment #107
danflanagan8Getting close!
Here's an updated patch with a fairly noisy interdiff. Here are the essentials.
user_maintenance_mode_subscriber / user\MaintenanceModeSubscriber, removed the newuser_maintenance_mode_request_subscriber / user\MaintenanceModeRequestSubscriber, and moved its functionality intouser_maintenance_mode_subscriber. I deprecated theMaintenanceModeSubscriber::onKernelRequestMaintenancefunction and removed it from the subscribed events. I removed the deprecation test because it seems like overkill for a deprecation that is not part of the BC promise.JsonapiMaintenanceModeRequestSubscriberback toJsonapiMaintenanceModeSubscribersince I had only added the wordRequestto match what was going on in the user module.Regarding followups, we'll want to remove the
maintenance_modeservice fromuser_maintenance_mode_subscriberonce the deprecated function is removed. I'll tag for followups.We'll also need to update the change record to note at least the following:
MaintenanceModeInterfacehas a new functiongetSiteMaintenanceMessageMaintenanceModeEvents::MAINTENANCE_MODE_REQUESTI'll add the tag for that. Do we need more than one change record for this stuff?
Comment #108
alexpottLooking good.
I think this priority doesn't make much sense. Imo it should be -1000 or something - like normally we want any other events to have a go first.
Comment #109
danflanagan8Thanks, @alexpott!
The way things are in the patch, there are three subscribers to
MaintenanceModeEvents::MAINTENANCE_MODE_REQUEST.\Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriberwith priority 40\Drupal\user\EventSubscriber\MaintenanceModeSubscriberwith priority 30\Drupal\Core\EventSubscriber\MaintenanceModeSubscriberwith priority -256First, is that the correct order?
Second, what would you set the priorities to?
Thanks!
Comment #110
bbralaOrder doesn't matter much except the
userone needs to be very late. -1000 as @alexpott suggest would be fine there. The jsonapi one (and user one for that matter) will only be triggered after theDrupal\Core\EventSubscriber\MaintenanceModeSubscribertrows the event.So, i think, just change the user one to -1000 and hopefully call it a day.
Comment #111
danflanagan8Thanks, @bbrala
But now I'm confused. Are we suggesting this?
\Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriberwith priority 40\Drupal\Core\EventSubscriber\MaintenanceModeSubscriberwith priority -256\Drupal\user\EventSubscriber\MaintenanceModeSubscriberwith priority -1000In this case I don't think the user one would ever be triggered and the user would never be logged out.
Comment #112
bbralaOrder should be:
\Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriber\Drupal\user\EventSubscriber\MaintenanceModeSubscriber\Drupal\Core\EventSubscriber\MaintenanceModeSubscriberIf the jsonapi wants to do its thing it should. Then the user one needs to work its magic if needed. Then if nothing happens we do through to the default.
Since the default priority for an event subscriber is 0. I think we should prefer these to run after that. So we need a negative priority. I think the following should be fine, I think only really matters between subscribers subscribed to the same event.
\Drupal\jsonapi\EventSubscriber\JsonapiMaintenanceModeSubscriber--800\Drupal\user\EventSubscriber\MaintenanceModeSubscriber--900\Drupal\Core\EventSubscriber\MaintenanceModeSubscriber--1000If we do the following, should a contrib module subscribe without giving a priority it will at least trigger before the core ones, which i think is a good idea.
Comment #113
danflanagan8Attached is a patch with updated priorities suggested by @bbrala in #112. Thanks!
Comment #114
alexpott+1 to the logic that's arrived at #113 nice work @danflanagan8 and @bbrala
Comment #115
bbralaThanks :)
You probably need an rtbc, setting to rtbc.
Comment #116
bbralaHmm, first let's fix the change record. The whole event subscriber think might need a separate one from the jsonapi change.
Comment #117
bbralachangerecordtags.needs framework manager reviewtag.Needs followuptag.Setting RTBC
Comment #118
danflanagan8Thanks @bbrala! I added a third CR related to MaintenanceModeInterface.
Here's an updated patch where all the CR references are correct (I think!)
I guess I have to set it back to NR, but it should be an easy review.
Comment #119
bbralaCheers, checked the url's. RTBC
Comment #121
mglamanDrupalCI is hitting grief with Composer 2.2, moving status back.
Comment #122
alexpottUnfortunately we need a 10.0.x version of the patch as well. I think there are some changes to the Symfony event system that we'll need to account for - so we'll need separate patches.
Comment #123
danflanagan8Here's an attempt at a re-roll for 10.0.x. Let's see what fails!
Comment #124
bbralaThat does seem like what is supposed to change. But now we wait ^^
Comment #125
danflanagan8I noticed something I messed up resolving one of the few merge conflicts. I added the return type back in here.
Comment #126
danflanagan8Well that green was surprising... :)
I guess it's ready for review again!
Comment #127
bbralaLooking good thanks.
Comment #128
alexpottCommitted aa3434c and pushed to 10.0.x. Thanks!
Committed b7b2a8b and pushed to 9.4.x. Thanks!
Comment #131
alexpott@bbrala @danflanagan8 can you write a release note in the issue summary? The current text can be improved :)
Comment #132
bbralaComment #133
bbralaFixed small typo in release note snippet
Comment #135
rar9 commentedpatch #118 no longer applies for D9.4.0
Comment #136
danflanagan8@Rar9, this is fixed in D9.4.0, so there's no need to apply a patch to 9.4.0.
Comment #137
mxr576FTR, Opened #3347601: Add Retry-After header to HTML maintenance mode responses as a follow up because a Retry-After header for HTML responses could be useful as well.