Flow 1.0.0
Flow project: Public API.
|
As of this writing the exact nature of where the project will permanently reside (and who will maintain it vs. use it) is in flux. Therefore for now I have removed the section covering certain topics and replaced it with the to-do you're reading. This should be undone when things settle down (obviously ensuring the brought-back section is made accurate). The topics to cover: "@author"
(including contact info); GitHub/other address indicating where to browse the project source; link(s) to the latest auto-generated web docs (if any); a section on the history of the project; and licensing info (if deemed needed) or pointer to it. (Reminder: Also update any similar sections of the historically significant net_flow::Node doc header.)
Since Flow gained its first users beyond the original author, some Flow-adjacent code has been written from which Flow can benefit, including a potential io
module/namespace for general networking/local I/O. (Flow itself continued to be developed, but some features were added elsewhere for expediency; this is a reminder to factor them out into Flow for the benefit of all.) Some features to migrate here might be: boost.asio extensions to UDP receive APIs to obtain receipt time stamps and destination IP (recvmsg()
with ancillary data extensions) and to receive multiple datagrams in one call (recvmmsg()
); boost.asio-adjacent facility to add custom socket options similarly to how boost.asio does it internally; boost.asio support for (local) transmission of native socket handles and security data over stream sockets (SCM_RIGHTS
, etc.).
Concurrent_task_loop::schedule_*()
APIs. Perhaps add bool in_loop_use_only
arg which, if false
, will always disable the single_threaded
optimization internally. At this time it always enables it if n_threads() == 1
which will cause thread un-safety if the returned handle is touched from outside an in-loop task. void
versions of the schedule_*()
APIs should be added which would lack this, as in that case there is no handle to misuse outside the loop. io_context
doc page. n_threads_or_zero
, est_hw_core_sharing_helps_algo
, est_hw_core_pinning_helps_algo
) can only be set at construction time even though start() and stop() can be invoked anytime. For instance a non-virtual
configure()
method could be added to each Concurrent_task_loop subclass, potentially with different signatures. Note, also, that the decision to have stop() exit the actual threads, in addition to making some things easier to code/reason about internally, is also more amenable to these dynamic changes – messing with threading behavior dynamically is easier if one can exit and re-spawn the thread pool. Single_thread_task_loop::schedule_*()
APIs. Perhaps add bool in_loop_use_only
arg which, if false
, will always disable the single_threaded
optimization internally. At this time it always enables it which will cause thread un-safety if the returned handle is touched from outside an in-loop task. void
versions of the schedule_*()
APIs should be added which would lack this, as in that case there is no handle to misuse outside the loop. Target_ptr
is deprecated and shall be always left at its default value in future code; eventually remove it entirely and hard-code the default value internally. Async_file_logger::log_flush_and_reopen(true)
is great for flushing, such as in an abort-signal handler, but providing just the flushing part without the reopening might be useful. At the moment we've left it this way, due to the vague feeling that closing the file upon flushing it is somehow more final and thus safer (in terms of accomplishing its goal) in an abort-signal scenario. Feelings aren't very scientific though. Logger
s, since it is an app-wide function and potentially would want to be reflected in multiple loggers. It could take a pair of iterators or a container (in both cases, of template argument type). Arguably, though, this is overkill; as (1) most code paths deal with only one get_logger()
-owning object each; (2) naming of a thread is obvious enough in subsequent logs (hence this message only calls attention to that operation plus notes the thread ID before the nickname assignment, not necessarily the most critical of information). Certainly this zero-to-one-Logger
version must continue to be available for syntactic-sugary convenience, even if the to-do is performed. async_*()
operations, meaning waiting for readability/writability/etc. but not performing the actual operation before calling the user handler, we provide a null_buffers
-style interface; like newer boost.asio versions we should deprecate this in favor of simpler async_wait()
APIs. This would apply to net_flow::asio::Peer_socket and net_flow::asio::Server_socket APIs. Similarly consider doing this for the sync_*()
operations in the non-asio
superclasses net_flow::Peer_socket and net_flow::Server_socket. Note that Event_set::async_wait() and Event_set::sync_wait() already exist; they are powerful but a bit complex to use in these simple situations. Hence the following hypothetical wrappers would be welcome replacements for the deprecated null_buffers
and "reactor-style" APIs in these classes: net_flow::Peer_socket::sync_wait(Event_set::Event_type)
, net_flow::asio::Peer_socket::async_wait(Event_set::Event_type)
, net_flow::Server_socket::sync_wait()
, net_flow::Server_socket::async_wait()
. (Timeout-arg versions would be desired also, as they exist now.) boost:any
s each of which stores either a Peer_socket::Ptr
or a Server_socket::Ptr
; Sockets should be changed to store C++17 std::variant
s. Performance, both internally and externally, would improve by using this type-safe compile-time mechanism (which is akin to union
s but much more pleasant to use). At the time this feature was written, Flow was in C++11, so variant
s were not available, and the author wanted to play around with any
s instead of haxoring old-school union
s. variant
is much nicer, however, and the dynamic nature of any
is entirely unnecessary here. Based on some outside experience, there maybe be problems – particularly considering the to-do regarding dual-stack IPv6/v4 support – in servers listening in multiple-IP situations; make sure we support these seamlessly. For example consider a machine with a WAN IP address and a LAN (10.x.x.x) IP address (and possibly IPv6 versions of each also) that (as is typical) binds on all of them at ANY:P (where P is some port; and ANY is the IPv6 version, with dual-stack mode ensuring V4 datagrams are also received). If a client connects to a LAN IP, while in our return datagrams we set the source IP to the default, does it work? Outside experience shows it might not, depending, plus even if in our protocol it does, it might be defeated by some firewall... the point is it requires investigation (e.g., mimic TCP itself; or look into what IETF or Google QUIC does; and so on).
Actively support IPv6 and IPv4, particularly in dual-stack mode (wherein net_flow::Server_socket would bind to an IPv6 endpoint but accept incoming V4 and V6 connections alike). It already supports it nominally, in that one can indeed listen on either type of address and connect to either as well, but how well it works is untested, and from some outside experience it might involve some subtle provisions internally.
Some of the ostream<<
operators we have take X*
instead of const X&
; this should be changed to the latter for various minor reasons and for consistency.
flow::net_flow should use flow::cfg for its socket-options mechanism. It is well suited for that purpose, and it uses some ideas from those socket-options in the first place but is generic and much more advanced. Currently net_flow
socket-options are custom-coded from long before flow::cfg existed.
ostream
output operators for Node and asio::Node should exist. Also scour through all types; possibly some others could use the same. (I have been pretty good at already implementing these as-needed for logging; but I may have "missed a spot.")
Server_socket objects closed as if by what?
close_final()
and close_start()
, but those are just ideas and may be replaced with timeout, or nothing. At this time, the only closing supported is abrupt close due to error or abrupt close via close_abruptly(). close_abruptly() return bool
(false
on failure)?
Currently this close_abruptly() is the only way for the user to explicitly close one specified socket. All other ways are due to error (or other side starting graceful shutdown, once we implement that). Once we implement graceful close, via close_start()
and close_final()
, use of close_abruptly() should be discouraged, or it may even be deprecated (e.g., Node
s lack a way to initiate an abrupt close for a specific socket).
Server_socket::close()
functionality – at least the equivalent of Peer_socket::close_abruptly(). S_REAL_HI_RES
would always be preferable. Nevertheless it would be interesting to "officially" see its characteristics including in particular (1) resolution and (2) its own perf cost especially vs. S_REAL_HI_RES
which we know is quite fast itself. This may also help a certain to-do listed as of this writing in the doc header of flow::log FLOW_LOG_WITHOUT_CHECKING() (the main worker bee of the log system, the one that generates each log time stamp). operator*=(Duration_set)
by a potentially negative number; same for division. timed_function() overload exists for a single Clock_type
, but simultaneous multi-clock timing using the perf::Clock_types_subset paradigm (as used, e.g., in Checkpointing_timer) would be a useful and consistent API. E.g., one could measure user and system elapsed time simultaneously. As of this writing this only does not exist due to time constraints: a perf-niggardly version targeting one clock type was necessary.
timed_function(), when operating on an atomic<duration_rep_t>
, uses +=
for accumulation which may be lock-free but uses strict ordering; a version that uses fetch_add()
with relaxed ordering may be desirable for extra performance at the cost of not-always-up-to-date accumulation results in all threads. As of this writing this can be done by the user by providing a custom type that defines +=
as explicitly using fetch_add()
with relaxed ordering; but we could provide an API for this.
Tight_blob<Allocator, bool>
, which would be identical to Basic_blob but forego the framing features, namely size() and start(), thus storing only the RAII array pointer data() and capacity(); rewrite Basic_blob in terms of this Tight_blob
. This simple container type has had some demand in practice, and Basic_blob can and should be cleanly built on top of it (perhaps even as an IS-A subclass). Lock_guard<Mutex_non_recursive>
possible. Remove it and all (outside) uses eventually. Lock_guard<Mutex_noop_shared_non_recursive>
possible. Remove it and all (outside) uses eventually. Shared_lock_guard<Mutex_noop_shared_non_recursive>
possible. Remove it and all (outside) uses eventually. Lock_guard<Mutex_recursive>
possible. Remove it and all (outside) uses eventually. Lock_guard<Mutex_shared_non_recursive>
possible. Remove it and all (outside) uses eventually. Shared_lock_guard<Mutex_shared_non_recursive>
possible. Remove it and all (outside) uses eventually. boost::shared_mutex
to std::shared_mutex
, as the former has a long-standing unresolved ticket about its impl being slow and/or outdated (as of Boost-1.80). However see also the note on util::Mutex_shared_non_recursive that explains why it might be best to avoid this type of mutex altogether in the first place (in most cases). boost::random::random_number_generator
, and should be written to conform to it, so as to be usable in std::random_shuffle()
, and also to gain its appealing extra features without losing any of the already-available simplicity. Note, however, that std::random_shuffle()
is deprecated (and gone in C++17) in favor of std::shuffle()
, which is available from C++11 on, and which works directly with a formal URNG such as Rnd_gen_uniform_range_base::Random_generator. So this to-do is less about that and more about gaining those other features while being suitable for random_shuffle()
, which some code does still use out there. We could eliminate schedule_task_from_now() potential limitation versus Timer wherein each call constructs (internally) a new Timer. A pool of Timer
s can be internally maintained to implement this. This may or may not be worth the complexity, but if the API can remain identically simple while cleanly eliminating the one perf-related reason to choose Timer over this simpler facility, then that is a clean win from the API user's point of view. By comparison, other possible improvements mentioned complicate the API which makes them less attractive.
schedule_task_from_now() and surrounding API provides an easy way to schedule a thing into the future, but it is built on top of boost.asio util::Timer directly; an intermediate wrapper class around this would be quite useful in its own right so that all boost.asio features including its perf-friendliness would be retained along with eliminating its annoyances (around canceling-but-not-really and similar). Then scheduled_task_from_now() would be internally even simpler, while a non-annoying util::Timer would become available for more advanced use cases. echan may have such a class (in a different project) ready to adapt (called Serial_task_timer
). I believe it internally uses integer "task ID" to distinguish between scheduled tasks issued in some chronological order, so that boost.asio firing a task after it has been canceled/pre-fired can be easily detected.
Add example usage snippets in Shared_ptr_alias_holder doc header, illustrating the pattern.
flow::util::Shared_ptr_alias_holder Const_target_ptr
is deprecated and shall be always left at its default value in future code; eventually remove it entirely and hard-code the default value internally.
alt_sstream
in boost.format; it doesn't seem to have public documentation but isn't in a detail
directory either. So that's interesting. It might have better performance than the implementation here (by being more "direct" probably). Then again it might not. Upgrade to newer Boost and keep testing timer resolutions on the above and other OS versions. Update: As of this writing we are on Boost 1.74 now. Needless to say, it is time to reassess the above, as it has been years since 1.50 – and we had been on 1.63 and 1.66 for long period of time, let alone 1.74. Update: No changes with Boost 1.75, the newest version as of this writing.
<<
and by implementing this as a variadic function template (C++11 feature).