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
Implement RPC version handshake #507
Conversation
Multiple separate comments:
|
That's correct.
True. I even thought about it and at some point decided that it's not worth it. This version handshake itself could rather easily be made non-breaking as you suggest. But the bigger problem is that the actual wire format change that Stéphane is working on would then also have to work alongside the current OCaml-dependent wire format. I don't know how realistic it is to keep supporting the current wire format. Definitely worth considering. I may extend this patch just to have the option, even if we end up not using it. |
Good point about how hard it might be to use a non-breaking version -- but if the patch itself is non-breaking, it can be applied sooner, with less angst. None of this is an easy call and I don't pretend to be all wise. |
I added what I call 2.51-compatibility mode. The name can be changed of course, this makes no difference to the concept. For the clients, this compatibility mode works automatically. For servers, there is a new switch which makes it serve all clients in 2.51 mode. (When using ssh, this switch must be passed from client with the Changes to the |
I had a look at @glondu's Umarshal implementation and the changes he'd done in the Remote module. I now think that supporting the current and new encoding in parallel is very feasible and could be set as an explicit goal. I've added a commit to make it as straightforward as possible to add Umarshal encoding functions in Remote and keep it backwards-compatibile at the same time. |
Rebased and refactored. Code is slightly longer now but hopefully somewhat easier to read. New: Added an extension mechanism to the handshake protocol. Handshake extensions are intended for low-level needs that could benefit from being executed before the full RPC mechanism is initialized. Extensions are always initiated from server side. One example of such handshake extension is authentication. Another example is version-independent negotiation of RPC encoding format (RPC version x is available both in binary format and JSON, for example). It is not required to merge the extensions mechanism before any actual extensions exist. It may also very well change between RPC versions now that version negotiation is the very first thing done. Version compatibility with this PR
|
Added a mechanism for server to automatically switch between the 2.51-compatibility mode and the new versioned RPC mode when using ssh. It only works over ssh and not with socket because with ssh the server is started by client, so client can pass extra information to server. Version compatibility with this PR
|
(Now that the release is done I'm coming back around to PRs that were obviously post-release.) It seems this is a break if using socket mode and the old pref isn't configured. I wonder about the wisdom of making servers do the old way unless configured to be new. I also wonder if socket mode should be deprecated and even removed; it seems like a security fail, and ssh, or some other kind of wrapper (TLS?) seems like the way to go. I'll float that on users. I also wonder about the extensions scheme; if that can be safely deferred I tend to want to defer it, as it feels speculative, and I'd like to avoid second system syndrome. |
Speaking only for myself (obviously), in my over 10 years of daily unison usage I have never used the socket mode and won't miss it. |
Agree about the extensions scheme. For a moment there I had a thought that this was the only opportunity to prepare for it. That is obviously false, so I've removed that commit. Now added complete 2.51 compatibility. It relies on tricks with the old protocol. See code comments for the details. Version compatibility with this PR
|
I added two commits with preparation work for keeping full 2.51 compatibility with future type changes. With this preparation, I can make the ACL and xattr patches completely non-breaking, for example. These two are not strictly necessary to merge with this PR but since they depend on the other commits then I chose to add them here for review instead of opening a separate PR. There are design choices and assumptions that I'll describe more in the hackers list. |
One more commit to make for a smoother upgrade from any version that uses archive format 22 (interestingly, this seems to be all versions back to at least 2.13 -- the archive format version has not changed during almost 17 years of history visible on GH). Note that this is just about upgrading, it is not related to client-server version differences. This, like previous commits, is just the foundation. The archive format has not changed yet. The ability to upgrade does not depend on why the archive format will change -- either because of switching from Like previous two commits, this one is not strictly necessary to merge with this PR but it makes sense to keep all the commits together as they form a solid foundation on which to base the |
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've reviewed until 651dd13, and except for the enableFlowControl function it looks good.
q.canWrite <- isServer; | ||
if q.canWrite then | ||
Lwt.ignore_result (popOutputQueues q >>= Lwt_unix.yield) | ||
|
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 calls to Lwt.ignore_result look suspicious here, especially the first one. Are you sure you don't want to wait for flushBuffer to be complete before setting q.flowControl to true? Given the contexts in which this function is used, I would turn it into a proper monadic function.
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 pretty much followed the example of allowWrites and disableFlowControl from the original code (... except, that doesn't explain the flushBuffer -- that's apparently all me). I've now changed it as you proposed (and fixed a bug I found while at it).
I reread all the comments, and it seems like we are in a state with no other mergeable PRs, so I think it's time to seriously consider merging this. If you concur, a rebase might be nice because it should be trivial, just to clean up history, perhaps take something off the table, and get CI artifacts with other recent changes. Then perhaps time to ask for more review, comments on the wisdom of merging, and testing. If you don't think it would be wise to merge now, please add a comment explaining why. |
Do a version handshake instead of relying on Unison version to check compatibility of client and server when opening the connection. This enables future changes while allowing backwards compatibility independently of Unison version.
Leverage RPC versioning to keep the old encoding functions usable even after the new encoding functions have been introduced.
Use the old protocol in a non-intended (but safe) way to notify both the server and the client that the other is capable of newer RPC version negotiation. This is transparent to old clients. Start connections in the backwards-compatible mode to enable 2.51 clients, then upgrade if the client is known to be capable of it.
Data types used in 2.51.x must remain unchanged for as long as compatibility is retained with 2.51 clients and servers. To allow code enhancements and cleanups that require data type changes, this patch adds a conversion layer at the interface, allowing easy conversion of all RPC call arguments and return values between new changed data types and 2.51-compatibles ones.
This patch creates a compatibility layer for types [Update.archive] and [Props.t] so that they can be changed while keeping compatibility with 2.51 versions.
Just like RPC/API compatibility with versions <= 2.51.5, it is possible to load archives written by those versions. This is done only once, if archives of the new format are not found. Archives in pervious format are automatically deleted when the new archives are successfully written. This code is intended to be temporary as its only purpose is to make the upgrade smoother. It is not required for communication between different versions.
Wait for buffer flushing to complete before continuing. Also, make it a monadic function and make sure it is sequenced properly in the 2.51 compatibility mode.
This patch can be reverted after the next release, when compatibility with OCaml < 4.04 is no longer required.
This patch can be reverted after the next release, when compatibility with OCaml 4.01 is no longer required.
I have rebased and added some temporary patches for compatibility with older compilers (I will create a separate PR to remove all such patches after the next release). |
Merge scheduled for 15 14Z January. |
As mentioned in the mail sent to unison-hackers list. This patch is intended to introduce a compatibility measure,
but while doing so, it is itself a breaking change. Edit: Compatibility with 2.51 is possible to keep.Background and motivation
With current implementation, when client opens the connection to a server, it checks compatibility by comparing a Unison version string sent by server. This is limiting when it comes to making backwards-compatible changes.
This patch implements a version handshake so that client and server can agree on a common RPC protocol version out of potentially several supported versions.
This is a low-level part of a fix to #407. A separate API-level mechanism is planned for a complete fix to #407.
Description
RPC version is defined as an integer. Versions do not have to be consecutive (or even positive, for that matter), only the value matters. Larger version values are always considered to be more recent than smaller version values. This patch sets the starting version at
1
but it could be any integer.A release could support several RPC versions (which do not have to be consecutive; could support versions 1 and 3 but not 2, for example). When client establishes a connection to the server, a version is selected which both client and server support, even if it is not the latest version. This allows a client to connect to both older servers (assuming the client supports an older RPC version) and newer servers (assuming the newer server supports an older RPC version).
The version handshake process is described in the code. The handshake process itself is a very simple text-based interaction.
What is it not?
This patch does nothing about incompatibilities caused by different OCaml versions.