Hi all,
This is my review of boost.afio. In fact I have still more comments on
the library, but this is taking already too long and I'm well past the
review deadline, so I'm sending what I have. I'm hopefully going to
post the second part in the next few days.
1. Should Boost.AFIO be accepted into Boost? Please state all
conditions for acceptance explicitly.
No, not at this time. While the library is potentially quite useful,
possibly to larger niche than expected to the author, I have many
issues with both the documentation and the API itself, which would
need to be resolved before acceptance.
To be clear, this is not a conditional acceptance. I believe a full
review is warranted to verify that the issues have been addressed.
The author is obviously quite experienced in this area (or at least
has done a lot of research), so I strongly encourage him to resubmit
the library in the future as it has the potential to be be a great
contribution to boost.
2. What is your evaluation of the design?
My preference, but wouldn't require for acceptance, would be to use ASIO async
protocol that leaves the choice of the completion notification
strategy to the user .
If the library wants all async functions to return futures, it should
follow the standard future idioms. First of all, having futures which
claim 'standard conformance' and then carry extra hidden error payload
is unacceptable. Also functions should take handlers as parameters and
not futures. The user should be responsible of explicitly 'then'
chaining futures. The library should follow the standard semantics for
handle types (i.e. single ownership move only wrappers system
handles), and similarly for the future wrappers for such handles
(i.e. future<handle>, not shared_future<handle>). Other reviewers have
discussed these issues better than I could, so I won't expand further.
The dispatcher should probably be called filesystem because that's what
it is.
The overridable per thread global dispatcher is evil. Functions should
take a dispatcher explicitly (or through a handle_ptr) or use a static
global dispatcher that represent the global filesystem.
Does the library need to distinguish between files and symlinks at
open time by calling differently named functions? I'll assume that's
the case, but I would like to know the rationale.
File, directory, symlink functions should be verbs and called
open_file, open_directory, open_symlink. They should return
differently typed handles (i.e. file_handle, dir_handle, sym_handle).
As an aside, the handle types should have value semantics and be movable.
To help generic code, rmdir, rmfile and rmsymbol should be unified
under the same function name (rm, or better delete). The parameter
type can be used to select the correct overload.
Functions that return handles must do so explicitly (i.e. no implicit
handle payload).
The library should *never* cause the program to crash because of
external causes (i.e. files and dirs disappearing or truncated,
external locks taken, lock forcibly removed, up to and including disk
failure). It is ok to abort on a failed internal consistency check,
although it would be nice if the check+abort was encapsulated in an
overridable macro (BOOST_AFIO_ASSERT maybe?). It is fine and even
encouraged for the check be present even in release builds.
The library *must* not suppose that every other program interacting
with the filesystem is using AFIO. It is fine if the library were to
give some additional guarantees (for example advisory locking becomes
non-advisory), but these guarantees should be properly documented.
For example, writes in append should be guaranteed atomic no matter
what the program doing the writes is doing. In practice, this means
that afio should document in detail the implementation of each
operation for each backend for each operation so that a knowledgeable
user can make the necessary inferences. In practice this seems already
to be mostly the case.
The documentation has a huge amount of notes on how the various FS and
OSs differ and what are the different guarantees. AFIO should provide
a standardised set of flags describing the various capabilities and a
query interface, so that the inevitable different code paths do not
need to be fs name dependent or, worse, #ifdef based. A good starting
point for deciding which flags to expose, are the various FS tables
scattered through the docs.
3. What is your evaluation of the implementation?
I haven't had a chance to do an as in depth evaluation. Additionally,
files with thousands of lines do not make it easy to read the
code. The git repo contains both v1 and v2 subdirectories, and I'm not
sure which one is the one actually under review (the docs say 1.5, not
sure which one it is)'
A few comments:
Overuse of shared_ptr is in my experience code smell as the unclear
ownership semantics lead to bugs down the line.
The amount of ifdefs is staggering. Most of them are probably
necessary, but the number of options that the library can be
configured with certainly doesn't help.'
It depends on external non-boost libraries, which it seems (but I
can't be sure because of the layer of versioning macros) inject names
outside the boost::afio namespace, which it shouldn't happen.
With two spinlock acquisition per move, the lightweight future/modand
seems anything but lightweight.
The library should really use boost::future instead of rolling its
own. The author should attempt to fold possible improvements directly
into boost.thread.
Similarly for any necessary improvements to Boost.Asio (for the WoW
issue) and Boost.Filesysterm (for boost::path).
The abstraction overhead seems excessive, especially for a library
suposedly IO bound. The author should at least try to mach the
performance of the naive openmp implementation.
BTW, nonvolatile ram based storage will be becoming mainstream within the
year, so the author might need to revisit very soon the relative cost
of various operations.
Random suggestions (feel free to ignore these):
- look into non temporal stores to help with cache pollution.
- have a libnfs backend + NFS over loopback for truly asynchronous
filesystem operations (performance is probably going to atrocious).
- experiment with the "O_NONBLOCK disk read" patches for the linux kernel.
4. What is your evaluation of the documentation?
As noted by others, the documentation does not seem to be of boost
standard quality; At times it feels more like a stream of
consciousness; often irrelevant or less important information is
presented before more important ones.
Disquss Comments do not belong in the documentation.
With JS disabled, some pictures are not shown, at least in the single page docs.
I'm less bothered than others about the 'try AFIO online button'. In
fact it would be nice to have such a button after each example (I'm
not suggesting that the author should provide it though).
Any reference to not-implemented features should removed and at best
moved to a separate section at the end of the documentation. A common
patters is:
"" AFIO implements
* A
* B (not implemented)
* C (not implemented)
* D (not implemented)""
which is, honestly, a bit ridiculous.
Similarly scattering references to preivious versions of the library
all over the documentation is confusing. These should go to a separate
history page and/or changelog.
The misuse of Terms of Art ('monad' 'precondition') seem
a bit worrying.
At every occurrence of the term 'filing system' my brain trips. For a
while I thought it meant something subtly different from the more
common 'filesystem' or 'file system'. This could be just me, so feel
free to ignore this unless others complain.
The documentation often references C++1z coroutines, which as far as I
understand are far from being standardisation ready. It would be
better if examples using boost.coroutine, boost.context or even
asio.coroutine were provided.
** Intro
The first paragraph is a nice introduction, but it should mention
prominently that AFIO helps prevents file system races.
Move the sub points of bullet 8 to top level, instead deemphatize and
sythetize the performance claims under a single bullet. Before
claiming performance, describle what the library can do.
Reduce any reference to APIBind to a single line (something 'AFIO can
be used as a standalone library thanks to APIBind', details elsewhere;
also do not claim that APIBind is a boost library). Remove any
references to backends from this section. Totaly remove references to
unimplemented backands. Supported compilers and platforms, jenkins and
uni tests etc do not belong here. Neither do links to dispatch
benchmarks.
You might want to mention that it is a C++11 library written as part
of the GSoC. A reference to ASIO might or might not belong here (No
ASIO class or concept seem to be part of the interface).
Remove the Batch section from the simple example. Also the rest should stand
by itself, without the comments.
About the example itself: I find it surprising that you call the
results using verbs and operations using names. A better naming
convention would be nice.
Also in general please prefer
type result = op(params);
to
type result(op(params));
at least when op is an important operation. The initialisation syntax
together with the noun based naming convention makes it harder to
parse examples.
** Cheat Sheet
The frist few paragraphs are interesting, but should go after the
table. The table as is not useful, other than an alternate index. I
would expecte something like; function name, paramenters, result, very
short description. I have no idea what the other columns of the table
are trying to convey. The related functionality at the end does not
belong here.
** Design Introduction and Rationale
In this page I would expect to find an analysis of a generic afio
async operation, why a certain interface was chosen, how futures help
compose operation, how an operation is implemented internally. For
every choice a discussion of the design tradeoff should be presented.
Unfortunately, after an interesting "why did I write this library",
the chapter loses itself. The version history does not belong here
unless is used to justify design choices, but other than saying that
previous versions where slower, no other information is conveyed.
In particular, at this point of the documentation I would expect a
thorough explanation of the concept of a FS race and the minimum
guarantees given by the library with respect to file system races.
Additionally as the library comes with its own future implementation,
a detailed explanations of why boost::futures is not sufficient should
be given.
As an aside, the author claims that the library is completely lock
free, and it would be wait free if not for the the use of
spinlocks. This does not makes any sense as the use of spinlocks also
make the library *not* lock free, unless the author is using a
non-standard meaning for these terms (again see previous comment about
misuse of Terms of Art).
** Write Ordering Constraint
The explanation on how sync behaves like a barrier and the description
of scheduling ordering and dependencies is very useful, and it should
be a great starting point for a formal treatment of race freedom.
The description of TripleGit is completely unnecessary and should be
removed.
** Compilation
This section is very confusing. Let' say I want the default (whatever
it is) and do not define anything. I get this:
After defining your build macros of choice, making latest AFIO
available to your code is as simple as:
#include "boost.afio/include/boost/afio.hpp" // latest version
#include "boost.afio/include/boost/v1/afio.hpp" // specific version
if being used standalone or if being used as part of Boost:
#include "boost/afio.hpp" // latest version
#include "boost/afio/v1/afio.hpp" // specific version
I have no idea what any of the above means.
** Boost AFIO Installation into Boost
Why is this information not part of the previous chapter? Any
reference to non boost.afio should be dropped (except possibly to a
link at the end to an external non boost distribution).
I understand the the external build instructions are useful for
reviewers, but they shouldn't have been part of the documentation
under review.
** Supported compilers
not much to say here other than, assuming AFIO is accepted, the table
would be static and reflect the specific released version instead of trunk.
** Example 1
All references to the macro based versioning should be dropped.
I have no idea why the example datastore has to return a
monad