Problem/Motivation

#512026: Move $form_state storage from cache to new state/key-value system removed the code that deleted form "cache" entries after submission. After discussion with @znerol and especially with the recent immutable form changes, we think that is wrong, and that we should in fact always delete them, because the only ones that shouldn't be deleted are immutable form caches, and those should never get there.

Proposed resolution

For now, just hacking it in there to see what happens...

Remaining tasks

User interface changes

API changes

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because it's a regression from 7.x that leads to some form resubmissions causing errors (see #16)
Issue priority Major because the regression would result in bugs for any form (core and contrib) that stores data in $form_state that is only valid for a single submission.
Disruption This wouldn't require modules to make any code changes, but for some modules, it might change the behavior of how users can use the module's forms in conjunction with a browser's Back button. For simple multistep forms (like core's "Preview" or "Add another item" buttons), this doesn't introduce any regressions, because $form_state can be successfully reconstructed on-demand. For contrib cases where $form_state can't be fully reconstructed (e.g., a wizard, or something that adds CAPCHA elements that persist a state of whether they've been already solved), this might break the ability to finish the last step of the form, use the Back button, and submit a successful second submission (without needing to start over at the first step), but this is the same limitation as exists in 7.x and earlier.

Comments

berdir’s picture

StatusFileSize
new830 bytes

Let's see what happens.

berdir’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 1: form-delete-cache-2473759-1.patch, failed testing.

wim leers’s picture

Issue tags: +scalability
berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new5.4 KB

Fixed tests and done properly now.

Calling it from the FormBuilder, might need to move that, but then I need to inject FormCache into the FormSubmitter :(

wim leers’s picture

Looks good to me, but should probably be reviewed by somebody with deeper Form API knowledge.

znerol’s picture

+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -490,8 +497,10 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
       if (!$form_state->isRebuilding() && !FormState::hasAnyErrors()) {
         if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
+          $this->formCache->deleteCache($form_state->getValue('form_build_id'));
           return $submit_response;
         }
+        $this->formCache->deleteCache($form_state->getValue('form_build_id'));

Perhaps I'm missing something, but it looks like this line could be added in between the first and the second if statement?

znerol’s picture

I think it would make sense to also test that we really delete form-state on form-submission. Not sure whether FormTestBase::simulateFormSubmission() will trigger the removal.

tim.plunkett’s picture

StatusFileSize
new4 KB
new2.3 KB

#8 Yes, we absolutely can use that (we need to flag it as a non-programmed submission.

Addressed #7 as well. We want to do it after doSubmitForm() runs, but before returning the response.

tim.plunkett’s picture

StatusFileSize
new8.39 KB

Ugh, wrong patch, right interdiff.

EDIT: The new tests I added pass in isolation, but not as part of the larger suite. Debugging now.

The last submitted patch, 9: no_pager_shows_on-2425535-31.patch, failed testing.

tim.plunkett’s picture

StatusFileSize
new8.8 KB
new708 bytes

We have a stray static to clear.

The last submitted patch, 10: form_caches_should_be-2473759-10.patch, failed testing.

effulgentsia’s picture

+1 to this patch from me. It makes 8.0.x consistent with 7.x and earlier versions. I also left a comment in #512026-180: Move $form_state storage from cache to new state/key-value system and suggest holding off on RTBC until people following that issue have had a chance to comment here.

berdir’s picture

@effulgentsia: Thanks. I forgot to mention that I already discussed this issue in person with @alexpott in Montpellier, he didn't remember why he thought that the form_build_id wouldn't be there and was OK with adding it back.

effulgentsia’s picture

Status: Needs review » Needs work

Inspired by #2465053-21: Drupal 8 only allows one user every 6 hours to register when page caching is enabled — caused by entity UUID in form state, I tried the following:

1. Create a node that you can comment on, then visit that node page.
2. Type in a comment, click "Preview". This creates a multistep workflow (i.e., CommentForm::preview() calls $form_state->setRebuild()), which causes $form_state to be persisted.
3. On the Preview step, click "Save". In HEAD, this does not delete the $form_state stored in step 2. With this issue, it should.
4. Use the back button. The browser is nice to show you the prior "Preview" step.
5. Click "Save" again.

In HEAD, this results in a fatal error, because the undeleted $form_state includes the serialized comment, which includes a UUID, and so step 5's Save attempts to create another comment with the same UUID. #2465053: Drupal 8 only allows one user every 6 hours to register when page caching is enabled — caused by entity UUID in form state might change it so that UUIDs don't get persisted in $form_state, but this still demonstrates a general problem of a form state becoming invalid after submission, prior to the 6 hour expiration.

With #12, I expected this to get fixed, but it doesn't, because alexpott was partially correct in #512026-146: Move $form_state storage from cache to new state/key-value system:

+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -488,8 +495,15 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
-        if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
+        $submit_response = $this->formSubmitter->doSubmitForm($form, $form_state);
+        // If this form was cached, delete it from the cache after submission.
+        if ($form_state->isCached()) {
+          $this->deleteCache($form_state->getValue('form_build_id'));
+        }

The 'form_build_id' is no longer in $form_state, because for entity forms, the doSubmitForm() invokes EntityForm::submitForm(), which calls $form_state->cleanValues(). Changing the above to:

$this->deleteCache($form['#build_id']);

fixes that problem.

With that change applied, the use case above works (a new comment gets saved), because $form_state gets deleted from storage in step 3, and so step 5 happens with a brand new, reconstructed $form_state. Note that a different use case might not work the same way. For example, a complex multistep workflow where there's critical information in the last step's $form_state that can't be newly reconstructed. In such a case, clicking back and resubmitting might result in some other error. But, it would be the same error as would happen in HEAD anyway if you waited for 6 hours and then did it. In other words, contrib modules building such workflows need to account for deleted form states regardless since they can expire.

NW for the change above plus we should probably expand the tests to cover entity forms.

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new5.48 KB
new2.56 KB
new11 KB

Adjusted the unit tests to not test get/set, just delete, and mimicked the ->cleanValues() call in the unit test.

Thanks to @Wim Leers for the clever suggestion on how to mimic hitting the back button in a web test!

tim.plunkett’s picture

Priority: Normal » Major

Bumping this up.

The last submitted patch, 17: form_caches_should_be-2473759-17-FAIL.patch, failed testing.

The last submitted patch, 17: form_caches_should_be-2473759-17-PASS.patch, failed testing.

effulgentsia’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

RTBC + added a beta evaluation to the IS.

effulgentsia’s picture

The table of patch files at the top of the issue is ordered wrong. Trying to at least move the latest patch to the top.

effulgentsia’s picture

ahem, trying again.

effulgentsia’s picture

Assigned: Unassigned » alexpott

Assigning to alexpott, because of #15 and because he originally authored the change to not delete after submission in #512026-146: Move $form_state storage from cache to new state/key-value system.

fabianx’s picture

RTBC + 1

catch’s picture

Leaving for Alex, but patch looks right to me.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Looks good to me too. Committed 189be72 and pushed to 8.0.x. Thanks!

  • alexpott committed 189be72 on 8.0.x
    Issue #2473759 by tim.plunkett, Berdir, effulgentsia: Form caches should...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.