-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tfstate file can be corrupted if operation cancelled #34266
Comments
Thanks for reporting this, @MJohnson459. When running outside of CDK for Terraform, Terraform CLI's behavior for SIGINT is essentially a state machine with three states:
With all of that said then, I think probably a first step here would be to try to understand how CDK for Terraform is participating in this process. When you are using CDK for Terraform to wrap Terraform CLI, your Ctrl+C will cause SIGINT to be sent to the CDK for Terraform process. That signal might also be sent to Terraform CLI, if CDK for Terraform is starting Terraform CLI in the same process group. One possible cause of the problem you observed here would be if CDK for Terraform is starting Terraform CLI in its same process group, therefore causing your Ctrl+C to be sent as SIGINT to both processes, but then CDK for Terraform sends its own separate SIGINT to Terraform CLI to try to command it to shut down, which would then nudge Terraform CLI into the "urgent abort" state. I'm not familiar enough with the CDK for Terraform codebase to know if I've found a relevant piece of code, but I do see some logic for sending Ctrl+C to Terraform CLI which seems to achieve its goal by running Terraform CLI in a pseudoterminal (pty) and then sending the Ctrl+C character code to that pseudoterminal, which presumably causes something running in that terminal to send a SIGINT to Terraform CLI. Therefore my theory above does seem plausible, but I'd need to ask the CDK for Terraform team to confirm. If that is indeed the problem, I think the fix for this would need to be in CDK for Terraform itself, making one of the following changes.
The main difference between these two options, I think, is that the second one would preserve Terraform CLI's ability to enter its urgent abort state if the user types Ctrl+C twice -- since Terraform CLI would be directly relating to the job control signal -- whereas the first one would make it CDK for Terraform's responsibility to decide whether to send a second SIGINT to Terraform CLI when Ctrl+C is pressed twice. |
Thanks @apparentlymart I tried and failed to do an It seems like by default, That said, I still believe the corruption is a bug in terraform as even in the case where terraform is forcefully shut down, it still shouldn't be able to corrupt the state. As a further example, when I was using All in all, saving to an intermediate file and swapping seems a safer way to manage it and avoid any of these problems. I'm not familiar with Go but I am happy to try implement this if you have some pointers. |
Hi @MJohnson459, If I recall correctly, the filesystem backend is implemented the way it is because of how file locking works on Windows: Terraform needs to hold a lock on the state snapshot throughout, and that blocks any kind of renaming or deleting of the existing object on disk. The local filesystem storage is intended primarily for trivial experimentation anyway, and we expect anyone using Terraform "for real" should be using one of the remote state backends, which (depending on the features of the underlying storage) bring additional safety such as the preservation of historical state versions. Even with local state, there should typically be a separate If we could find a different design that would work across all platforms Terraform supports -- the primary supported platforms are Windows, macOS, and Linux, and the other best-effort-supported unixes tend to follow from what we do for Linux and/or macOS -- then that would be great! I believe we did look for other options at the time of originally implementing this and ended up concluding that we could either have locks or atomic updates and decided that locks were more important, and the locking behavior is now protected by the Terraform v1.x Compatiblity Promises and so we cannot regress it -- but if we can find a way to achieve both together then of course that would be ideal. |
My use case is generally large but short term deployments which the local backend seems ideally suited to. Using cloud storage would add a fair bit of complexity and I'm not sure we would get much benefit from it. Would you still recommend using cloud backends for jobs that only last for a couple of hours with no collaboration? Regarding the proposed change, it looks like it would be fairly straight forward but there are definitely bits I don't understand. This comment does imply the the intention was to swap the files in the future but I guess things may have changed since then. If I understand the code correctly, it
The question is really which bit takes a long time. Is there a reason that step 2. couldn't be be delayed or 4. write to an intermediate file first? |
I think the main constraint as I understand it is that the following cannot both be true at once:
The typical way to achieve 2 on Unix is to make a new file and then rename it over the old one, but that invalidates 1. As far as I remember, even that wasn't possible on Windows where holding the lock forces the file's identity to stay constant. Currently Terraform is prioritizing 1 at the expense of 2, which in the worst case means that Terraform can potentially be interrupted while the content of the file is not valid. The comment in the file is reflecting that we wanted to find a way to achieve both at once. If we can find a design that does so without breaking any existing guarantees (due to the 1.x Compatibility Promises) then we would like to adopt it, and the question would then just be how to mitigate the risk of making that change. One interesting constraint in the mix here is that in particularly-awkward cases the two systems competing to acquire a lock might not be running the same version of Terraform CLI, and in particular one might be running the current implementation which expects the lock to be held on the same file containing the state data. This constraint has tended to be the upset that's always got in the way of my first idea in this area, which is to use a separate file (whose content is irrelevant) as the holder of the lock, so that other files around it can then change identity without affecting the locked file. My brain is in a very different place than this right now and so I'm afraid I don't have any more context ready to share off the top of my head than what I've shared already, but if you have any specific design ideas then one of my colleagues on the team can hopefully participate in a more detailed discussion about them! |
Thanks @apparentlymart that makes sense. I did a quick and dirty profiling using some extra logging after every statement to see where the time was actually being spent. It might be that there could be a good improvement just by moving the Truncate later. Splitting the
The main thing was the encoding in Moving the In the ideal case, if we could move the Anyway, I'm going to look into using a remote backend as suggested which should avoid this for us at least. |
Thanks for that extra context, @MJohnson459! I wonder if @jbardin has some further thoughts here, since I think he's the one that's spent most time in this part of the system in the past. |
I agree that moving the truncate call to after the marshal check would be a safe way to lessen the time spent with no state when there is a lot of data, though that's not really a fix for the problem. One thing we might be able to do is create platform-specific methods for the |
Terraform Version
Terraform Configuration Files
N/A
Debug Output
N/A
Expected Behavior
Note: I originally thought this was a
cdktf
bug but it seems this issue is related toterraform
directly. The expected behaviour and reproduction steps still usedcdktf
although it shouldn't be necessary to reproduce.I am running
cdktf destroy
via a python usingsubprocess.run
. The exact command isWhen this is destroying instances, pressing
ctrl +c
should stop the destroy and leave theterraform.<stack>.tfstate
file with an accurate list of state.Actual Behavior
The
terraform.<stack>.tfstate
file is empty.Steps to Reproduce
Note: The below instructions are one way to induce the issue but it is likely there are others. Using raw
terraform
it should be possible if the state is large and thedestroy
command is exited forcefully enough.cdktf destroy
via pythonctrl +c
.It seems like the
python
subprocess call is required for the state to be emptied permanently, however using a rawcdktf destroy
I noticed that the state file is briefly emptied and then recreated. This seems dependent on the size of the state. I can reliably reproduce this with a ~5MB state file.Additional Context
I believe this issue is related to the
TODO
comment herehttps://github.com/hashicorp/terraform/blob/1f9734619f953ecc7252d7b98a6d40d751b4ea1e/internal/states/statemgr/filesystem.go#L124C1-L129C18
With a large state file, an interrupt can happen between clearing the file, and the updated state being completely written.
The solution is likely what the comment suggests. Write the state file to a temporary file and then do a "swap" to update the state file. This would ensure the state file is never in a "corrupted" state.
References
I originally reported this against under cdktf hashicorp/terraform-cdk#3269
The text was updated successfully, but these errors were encountered: