[Unison-hackers] Keeping compatibility with 2.51 on type level

Tõivo Leedjärv toivol at gmail.com
Tue Jul 20 15:04:30 EDT 2021


I have added two more commits to PR
https://github.com/bcpierce00/unison/pull/507

Since the PR is now fully compatible with 2.51, there is yet another
piece of the puzzle that must be solved if we want to keep that
compatibility in the future.

Due to 2.51 using OCaml's Marshal module for on-the-wire encoding then
in addition to requiring mostly matching OCaml versions, also the
types on both ends must match exactly. And this is precisely what
these two additional commits are about.

The code is there for review, this mail is just to clarify some of the
assumptions and design decisions I made. As a background for why this
matters and why it's needed: with this foundation I can make for
example the ACL and xattr patches non-breaking.

- The assumption is that the only way to remain compatible with 2.51
  is to keep unchanged the types that are exchanged between client
  and server.
- To that end, the types that are changed (due to new features, bug
  fixes, you name it) must be duplicated: one type for the actual code
  to function and the other type that is an exact copy of how it
  existed in 2.51 <= 2.51.4.
- That copy of a type is never used in the then-current code, it is
  there only to provide on-the-wire compatibility with 2.51. As such,
  the data will be converted between the types at the moment of
  marshaling and unmarshaling with the help of the `Marshal` module.
- Each RPC call takes type-specific conversion functions (optional,
  supplied only if the types have changed).
- While easily extendable to future version changes, I have foreseen
  this mechanism to be used only for compatibility with 2.51. Managing
  type changes between future versions (those using Stéphane's Umarshal,
  for example) will have basically the same mechanism built-in (by
  making dynamic/conditional the definition of "schema" for marshaling
  and unmarshaling a certain type (it will be static for most types,
  since they don't change between versions)).
- In the code, I decided to keep the copies of the so-called
  compatibility types in the same module, next to the "current" types.
  Likewise with any functions that must be duplicated to work with the
  compatibility types (besides the conversion functions, these would
  be very rare). I did it mainly for simplicity and code locality
  (compatibility code is right next to the code it provides compatibility
  for). Perhaps a cleaner solution would be to add a V0 sub-module,
  but my OCaml skills don't see how to make it work in a simple way.

In the commits you can see an example with `Update.archive` and
`Props.t` preserved in their 2.51 state. Note that in addition to
plain data conversions, the `checkArchive` function must be preserved
as well as it's result on both ends must match and is directly
dependent on the entire data structure. I have verified that this
works and retains full compatibility with 2.51 even if the current
`Props.t` is changed.

And to be complete, it has to be mentioned that this approach only
works as long as the new code does not break with the data conversions
(which by definition are lossy).


More information about the Unison-hackers mailing list