-
Notifications
You must be signed in to change notification settings - Fork 13
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
Change walproposer protocol to run on libpq #60
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that using libpq protocol everywhere is good idea. But...
I think that one of the primary motivation for them is to simplify implementation and reuse existed libpq code. But this patch is adding almost three times more lines then removing. And logic of state machine becomes even more complicated. May be new implementation is more correct and correctly handle problems which are not addressed by old implementation. But at least just by looking at the code I can not ensure than new FSM really works correctly. Now there are three different sets of states: wal keeper state, poll state and socket state. I am not sure if it really needed and make code more clear and maintainable. But I can not propose some alternative solution. Using coroutines is definitely peferrable that manual implementation of FSM, but as far as walproposer is embedded in Postgres and has to be implemented in C, there are almost no other choices...
@knizhnik Those are all fair points -- a few things that I think are worth mentioning:
That being said, there's definitely a lot going on here. I personally think the new structure of |
15a576e
to
fe24ac2
Compare
First of all, I like affluence of comments and general approach is correct and complete. However, I think there is still quite a potential to make things simpler and have less code. Fundamentally, I'd keep the previous structure of single
Also, as inline comment says, we must track reading on socket even in states like So roughly, I imagine the skeleton of
As said above, realistically there are no other states except All other not-network-triggered advancements (sending new piece of WAL in Last and least, some bikeshedding:
|
* sometimes called when the walkeeper isn't waiting for anything, and so the best thing to do | ||
* is just nothing. | ||
*/ | ||
if (wk->sockWaitState != WANTS_NO_WAIT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually never WANTS_NO_WAIT
, because even if we don't expect anything we still listen to read notifications to notice EOF
on it. So this as well as remove_if_nothing
is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense. At first glance, properly making this change would be tricky, so I think the best thing to do is probably to figure out the best way to remove WKSockWaitKind
entirely.
Also -- if we always require some kind of valid socket condition, then we introduce an edge case for SS_OFFLINE
. Granted, it's a small edge case, but it's the sort of thing that I'd be concerned about introducing because it has the potential to add hard-to-reproduce bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the best way to remove WKSockWaitKind entirely.
+1
then we introduce an edge case for SS_OFFLINE
Which is fine, as it must never get into UpdateEventSet, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, you're right. UpdateEventSet
probably doesn't need anything more than an assertion that the walkeeper isn't offline then
@arssher Lots of stuff to go through! Some of this, I mentioned in our chat earlier, but I'm reiterating it here just to have it. In general, the list you've made makes a lot of sense. The big point seems to be that there's many operations where -- even though the returns from libpq allow otherwise -- we expect them to immediately succeed (so we can use non-blocking writes). This seems to encompass points 1, 2, & 5. I'll work through them, commenting if any obstacles show up. As for restructuring to go back to the previous structure, I'd like to provide some of the reasoning behind why I moved away from that. When I was first trying to understand how the previous implementation worked, I found it very difficult to piece together the state transitions. I've since added some comments to the definition of This reason is why I've taken special care to order all of the cases in the walkeeper state switch to go in the order that their logic is executed. There's a clear flow in the way that each state (on success) moves to the state listed immediately below it (even if there might be some IO required first, so it's not executed immediately). There are definitely other things that I think should be changed about this implementation though -- either before merging, or immediately afterwards. I agree that it's overly complex to have three different fields encoding the state. If libpq will actually let us send small CopyData messages without blocking, then I there's maybe few enough states that it makes sense to collapse all of the polling logic into the states themselves. I understand that your proposed solution would have much less duplicated code if everything was assigned a state as-is, but I think that we can improve this enough by introducing some helper functions that it shouldn't make too much of a difference. The tl;dr is basically that I originally chose this design because I thought the structure was significantly easier to understand and expresses it more clearly (even if it's currently more messy because it hasn't been polished yet). If you strongly believe that the previous structure is better, I'm happy to change what I've written -- I just do not think it is a good idea. Re: bikeshedding
|
This last commit (8e30d7d) seems to fix the problem I was having, but I'm not actually sure what the root cause is. Basically, we were getting this sequence of actions in our tests:
AFAICT, because the WAL proposer restarts the connection (and starts waiting) before the WAL acceptor is back up, the socket it's waiting on is never write-ready, as is required for the first step of connection polling. (relevant docs) I'm wondering if the recommended usage given by the docs fails to account for certain kinds of invalid connections, but I would have assumed they already had that handled. |
Yeah, I see the same behaviour. However, I don't think this is normal, i.e. timeouts shouldn't be the cure. Note that if you attach with gdb and call |
Oh interesting, ok. Perhaps we have to call PQconnectPoll always without waiting for the socket to become write-ready? I replicated the same behavior as I found elsewhere in postgres (here), but maybe that's incorrect. Another possibility: Maybe we check if it's read-ready when we're expecting write-ready -- that could indicate the socket is disconnected. Tossing out some ideas here |
Hm-hm. Looks like @knizhnik was right above that
that's what most probably happens here: previous socket is removed from waiting set completely as it was closed by libpq. |
Huh! When I was looking over the logs, I thought we never actually polled the connecting state (and so didn't give the socket a chance to change), but I guess that must have been incorrect. Thanks for the help :) |
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
157c7c7
to
04cfa32
Compare
Not 100% the previous, but what I find important is 1) avoid repetition of libpq-related logic, i.e. doing all polling (writing these chatty switches handling all possible outcomes) in a single place; 2) Have clearly trackable flow for each state, which IMV means there should be one top-level switch instead of two -- currently you have switch on Idea to have the states listed sequentially is clear but, well, since they share libpq logic the control flow is mixed anyway. OTOH, you have them listed like that in enum declaration + have English description on the protocol (which should be updated BTW). As for two switches, I see the idea of separating libpq polling to |
Re: Re: bikeshedding
You mean isolating caller from handling
It doesn't; what I meant is from readability POV the impossible states is the last thing you want to know about this switch because they are impossible. Sequential order is another valid point, so here we also have to compromise. |
Yeah - I'd really like them to be that way if possible. I have some ideas for unifying control flow and reducing boilerplate that I'll have to flesh out a little more to judge them. The piece you'd mentioned above seems like a good idea:
Reading is more tricky, but a similar idea should be possible, and I think this can cut down on boilerplate.
Definitely, yeah. In a similar vein to above, we can (and should) just get rid of this.
Yup - that case is probably the weakest example of this, so it's maybe not worth it - as you've said. I do think something like the return on |
Going to merge this -- I opened #66 to keep this going. I'll follow up with a new PR for changes once they're somewhat implemented. |
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Per feedback on #60; move calls to PQconsumeInput into the libpqwalproposer-specific functions get_query_result and async_read. Simplifies things a bit - still leaving SMOD_ALL as a flag in case there's future changes, even though there's now only one modifier.
Per review feedback on #60, changes most of walproposer's writes to blocking writes, given that they are small and shouldn't actually have a delay associated to them if the socket is open for writing. Because most of the writes have been changed to blocking, there's no longer a need for state modifiers.
Per feedback on #60; move calls to PQconsumeInput into the libpqwalproposer-specific functions get_query_result and async_read. Simplifies things a bit - still leaving SMOD_ALL as a flag in case there's future changes, even though there's now only one modifier.
Per review feedback on #60, changes most of walproposer's writes to blocking writes, given that they are small and shouldn't actually have a delay associated to them if the socket is open for writing. Because most of the writes have been changed to blocking, there's no longer a need for state modifiers.
Per feedback on #60; move calls to PQconsumeInput into the libpqwalproposer-specific functions get_query_result and async_read. Simplifies things a bit - still leaving SMOD_ALL as a flag in case there's future changes, even though there's now only one modifier.
Per review feedback on #60, changes most of walproposer's writes to blocking writes, given that they are small and shouldn't actually have a delay associated to them if the socket is open for writing. Because most of the writes have been changed to blocking, there's no longer a need for state modifiers.
Per feedback on #60; move calls to PQconsumeInput into the libpqwalproposer-specific functions get_query_result and async_read. Simplifies things a bit - still leaving SMOD_ALL as a flag in case there's future changes, even though there's now only one modifier.
Per review feedback on #60, changes most of walproposer's writes to blocking writes, given that they are small and shouldn't actually have a delay associated to them if the socket is open for writing. Because most of the writes have been changed to blocking, there's no longer a need for state modifiers.
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
The majority of work here is going to be heavily cleaned up soon, but it's worth giving a brief overview of the changes either way. * Adds libpqwalproposer, serving a similar function to the existing libpqwalreceiver -- to provide access to libpq functions without causing problems from directly linking them. * Adds two new state components, giving (a) the type of libpq-specific polling required to move on to the next protocol state and (b) the kind of socket events it's waiting on. (These are expected to be removed or heavily reworked soon.) * Changes `WalProposerPoll` to make use of a slightly more specialized `AdvancePollState`, which has been completely reworked.
Closes #66. Mostly corresponds to cleaning up the states we store. Goes back to single states for each WalKeeper, and we perform blocking writes for everything but sending the WAL itself. A few things have been factored out into libpqwalproposer for simplicity - like handling the nonblocking status of the connection (even though it's only changed once).
Corresponding PR for neondatabase/neon#366.