So, I worry that the Database Connection cannot be closed properly!
You shouldn’t depend on that anyway, since your client can crash/disappear for all sorts of reasons without being able to run shutdown code.
If you need complex database updates (e.g. a sequence of related database operations), those should run inside a transaction so that the database isn’t left in a ‘partial update state’ when a client suddenly disappears.
The target Database is SQL Server. I remember, the Database Connection session always exists if the relevant Connection.Close() method is not called. And then, in order to free the occupied resources on the database server, the DB maintainer can only kill the relevant sessions manually, or even restarts the Database Service…
I used to frequently use “try… catch… finally…” or “try… finally…” in C#, and I regarded the “defer” block in Zig as the “finally” block in C# originally. Now, I realize that the “defer” block in Zig is unequal to the “finally” block in C#…
In that case and with what @floooh wrote just now, maybe you should make a small wrapper.
As in one process which only establishes the connection to the database, gives the socket to a child process which does the relevant work you want to do with the database, waits for the child to exit, after the child exited for any reason, closes the database connection and then informs its parent process about what the child process did.
This way the reasons for why the “connection management”/wrapper process can exit is minimal and you can better guarantee that the connection is getting closed.
Yeah, but in some systems you have to guarantee that. But in these cases you normally don’t use general purpose operating systems.
But even if you do, you can severely minimise the reasons for why the client could crash/disappear to a point where all the remaining reasons would be a user (e.g. users send SIGKILL) or device error (e.g. power loss).
A panic should be used only when a clean shutdown is inevitable anyway.
That is, in very rare circumstances, such as out of memory conditions.
In this case, either use a custom panic handler, or rely on the Dead Connection Detection on the DB server side. I’m not using SQL server, but I’m pretty sure MS supports this feature somehow.
However, this will result in delayed freeing of the resources (such as locks), which may prevent other users/processes from doing their work until DCD cleans up.
The idea of using a separate process is not realistic from my experience (using Oracle for decades, though not from Zig yet).
For normal errors, better use errors, and maybe use a thread-local `rollbackAdvised` variable(?) which your code checks before performing a commit.
Just to be clear about this: catch and finally are not executed in C# if a panic occurs in the try block. It only works with exceptions. In Zig, defer and errdefer work with errors so it’s pretty equivalent in this regard.
Yes! Every possible error should be returned and handled with “try” or “catch” properly although to expect every possible error is not easy. Otherwise, the defer block and errdefer block won’t work as expected.
Sometimes I am confused in these discussions about what is being said about panics and I wonder if it is because I do not use Release{Fast,Small}, I use ReleaseSafe for deployment. Therefore, panics will occur if there is a bug such as an out-of-bounds index, so it is unrealistic to think that panics will never occur, or that I can avoid them completely somehow. Do you think most people in these discussions are assuming use of Release{Fast,Small} in production?
I understand your confusion. In fact, my attitude towards panic has also undergone a drastic change. I used to be a believer in fail fast. This view was deeply influenced by erlang’s “let it crash”, believing that when facing logical problems, crashing is a safer behavior than throwing recoverable errors. I believe there are quite a few people who hold this view, especially as the emerging rust language has institutionalized this concept. It’s just that my perspective has changed now. I think a more reasonable way to deal with ReleaseSafe is to throw an error rather than panic. The difference from panic is that throwing an error can correctly clear other external resources when exiting, and the main body of the program can have the option not to be held back by secondary modules for core functions. However, in any case, panic is still safer compared to undefined behavior.
Overall, my current attitude is: apart from panics for debugging purposes, under ReleaseSafe, errors should be thrown actively as much as possible rather than panics actively, unless an error that is truly and absolutely impossible to make the program run normally occurs, such as memory corruption. However, for behaviors such as array out-of-bounds, under ReleaseSafe, I will accept their default panics. In any case, the security of crashes is still higher than that of illegal behaviors.
Don’t worry too much. For errors that can’t be handled, throw them all the way to main until they are finally thrown from main. You can still exit the program, but the difference is that throwing errors up can perform all resource release processes in the middle.
Since an unexpected state indicates a logic bug, how would you know whether the program can continue to run normally? I don’t think I’m capable of determining that accurately.
A logical error means that the logic of the current module cannot run normally. Throwing an error up has achieved the purpose of stopping the logic from continuing to run.
However, the logical issues within the current module are isolated from those of other modules in the program. As long as there is no memory corruption and the data outside the module boundary is still considered normal, the remaining external resources should still be released before the operation is halted.
Only the caller knows the context. When a certain module at the bottom layer encounters data that does not meet expectations, it has no right to determine the life or death of the entire process. Direct panic is a kind of “lower-level aggression against higher-level”, where the underlying implementation details kidnap the top-level business logic.
Distributed systems have long accepted that “partial failure” is the norm, and the same should apply to single-machine programs. The only difference between stand-alone programs and distributed systems lies in that the memory corruption problem of stand-alone programs may lead to unexpected results. But conversely, as long as zig implements runtime memory corruption detection, this will no longer be an issue, and at this point, logical problems should not be a legitimate reason for panic.