summaryrefslogtreecommitdiffstats
path: root/fnet
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /fnet
Publish
Diffstat (limited to 'fnet')
-rw-r--r--fnet/.gitignore10
-rw-r--r--fnet/CMakeLists.txt34
-rw-r--r--fnet/INSTALL22
-rw-r--r--fnet/OWNERS2
-rw-r--r--fnet/README26
-rw-r--r--fnet/RELEASEINFO788
-rw-r--r--fnet/build/buildspec.xml39
-rw-r--r--fnet/ethereal/Makefile.am26
-rw-r--r--fnet/ethereal/Makefile.nmake27
-rw-r--r--fnet/ethereal/moduleinfo.h18
-rw-r--r--fnet/ethereal/packet-fnetrpc.c389
-rw-r--r--fnet/index.html15
-rw-r--r--fnet/src/.gitignore10
-rw-r--r--fnet/src/Doxyfile939
-rw-r--r--fnet/src/examples/frt/rpc/.gitignore16
-rw-r--r--fnet/src/examples/frt/rpc/CMakeLists.txt60
-rw-r--r--fnet/src/examples/frt/rpc/echo_client.cpp94
-rw-r--r--fnet/src/examples/frt/rpc/rpc_callback_client.cpp107
-rw-r--r--fnet/src/examples/frt/rpc/rpc_callback_server.cpp69
-rw-r--r--fnet/src/examples/frt/rpc/rpc_client.cpp92
-rw-r--r--fnet/src/examples/frt/rpc/rpc_info.cpp140
-rw-r--r--fnet/src/examples/frt/rpc/rpc_invoke.cpp107
-rw-r--r--fnet/src/examples/frt/rpc/rpc_proxy.cpp254
-rw-r--r--fnet/src/examples/frt/rpc/rpc_server.cpp125
-rw-r--r--fnet/src/examples/ping/.gitignore9
-rw-r--r--fnet/src/examples/ping/CMakeLists.txt17
-rw-r--r--fnet/src/examples/ping/packets.cpp65
-rw-r--r--fnet/src/examples/ping/packets.h36
-rw-r--r--fnet/src/examples/ping/pingclient.cpp93
-rw-r--r--fnet/src/examples/ping/pingserver.cpp65
-rw-r--r--fnet/src/examples/proxy/.gitignore4
-rw-r--r--fnet/src/examples/proxy/CMakeLists.txt8
-rw-r--r--fnet/src/examples/proxy/proxy.cpp244
-rw-r--r--fnet/src/examples/test/.gitignore0
-rw-r--r--fnet/src/examples/timeout/.gitignore4
-rw-r--r--fnet/src/examples/timeout/CMakeLists.txt8
-rw-r--r--fnet/src/examples/timeout/timeout.cpp93
-rw-r--r--fnet/src/testlist.txt17
-rw-r--r--fnet/src/tests/.gitignore3
-rw-r--r--fnet/src/tests/connect_thread/.gitignore1
-rw-r--r--fnet/src/tests/connect_thread/CMakeLists.txt8
-rw-r--r--fnet/src/tests/connect_thread/connect_thread_test.cpp26
-rw-r--r--fnet/src/tests/connection_spread/.gitignore1
-rw-r--r--fnet/src/tests/connection_spread/CMakeLists.txt8
-rw-r--r--fnet/src/tests/connection_spread/connection_spread_test.cpp83
-rw-r--r--fnet/src/tests/databuffer/.gitignore4
-rw-r--r--fnet/src/tests/databuffer/CMakeLists.txt8
-rw-r--r--fnet/src/tests/databuffer/DESC2
-rw-r--r--fnet/src/tests/databuffer/FILES1
-rw-r--r--fnet/src/tests/databuffer/databuffer.cpp201
-rw-r--r--fnet/src/tests/examples/.gitignore1
-rw-r--r--fnet/src/tests/examples/CMakeLists.txt8
-rw-r--r--fnet/src/tests/examples/FILES1
-rw-r--r--fnet/src/tests/examples/examples_test.cpp246
-rw-r--r--fnet/src/tests/fdselector/.gitignore4
-rw-r--r--fnet/src/tests/fdselector/CMakeLists.txt8
-rw-r--r--fnet/src/tests/fdselector/DESC1
-rw-r--r--fnet/src/tests/fdselector/FILES1
-rw-r--r--fnet/src/tests/fdselector/fdselector.cpp220
-rw-r--r--fnet/src/tests/frt/memorytub/.gitignore6
-rw-r--r--fnet/src/tests/frt/memorytub/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/memorytub/DESC1
-rw-r--r--fnet/src/tests/frt/memorytub/FILES1
-rw-r--r--fnet/src/tests/frt/memorytub/memorytub.cpp124
-rw-r--r--fnet/src/tests/frt/method_pt/.gitignore6
-rw-r--r--fnet/src/tests/frt/method_pt/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/method_pt/DESC2
-rw-r--r--fnet/src/tests/frt/method_pt/FILES1
-rw-r--r--fnet/src/tests/frt/method_pt/method_pt.cpp395
-rw-r--r--fnet/src/tests/frt/parallel_rpc/.gitignore1
-rw-r--r--fnet/src/tests/frt/parallel_rpc/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp129
-rw-r--r--fnet/src/tests/frt/rpc/.gitignore12
-rw-r--r--fnet/src/tests/frt/rpc/CMakeLists.txt29
-rw-r--r--fnet/src/tests/frt/rpc/DESC1
-rw-r--r--fnet/src/tests/frt/rpc/FILES3
-rw-r--r--fnet/src/tests/frt/rpc/detach_return_invoke.cpp69
-rw-r--r--fnet/src/tests/frt/rpc/invoke.cpp938
-rw-r--r--fnet/src/tests/frt/rpc/session.cpp124
-rw-r--r--fnet/src/tests/frt/rpc/sharedblob.cpp256
-rw-r--r--fnet/src/tests/frt/values/.gitignore1
-rw-r--r--fnet/src/tests/frt/values/CMakeLists.txt8
-rw-r--r--fnet/src/tests/frt/values/FILES1
-rw-r--r--fnet/src/tests/frt/values/values_test.cpp207
-rw-r--r--fnet/src/tests/info/.gitignore4
-rw-r--r--fnet/src/tests/info/CMakeLists.txt8
-rw-r--r--fnet/src/tests/info/DESC1
-rw-r--r--fnet/src/tests/info/FILES1
-rw-r--r--fnet/src/tests/info/info.cpp89
-rw-r--r--fnet/src/tests/locking/.gitignore8
-rw-r--r--fnet/src/tests/locking/CMakeLists.txt23
-rw-r--r--fnet/src/tests/locking/DESC1
-rw-r--r--fnet/src/tests/locking/FILES2
-rw-r--r--fnet/src/tests/locking/castspeed.cpp219
-rw-r--r--fnet/src/tests/locking/drainpackets.cpp134
-rw-r--r--fnet/src/tests/locking/dummy.cpp9
-rw-r--r--fnet/src/tests/locking/dummy.h17
-rw-r--r--fnet/src/tests/locking/lockspeed.cpp177
-rw-r--r--fnet/src/tests/printstuff/.gitignore1
-rw-r--r--fnet/src/tests/printstuff/CMakeLists.txt8
-rw-r--r--fnet/src/tests/printstuff/FILES1
-rw-r--r--fnet/src/tests/printstuff/printstuff_test.cpp45
-rw-r--r--fnet/src/tests/regress/databuffer/.gitignore0
-rw-r--r--fnet/src/tests/regress/fdselector/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/memorytub/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/method_pt/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/rpc/.gitignore0
-rw-r--r--fnet/src/tests/regress/frt/values/.gitignore0
-rw-r--r--fnet/src/tests/regress/info/.gitignore0
-rw-r--r--fnet/src/tests/regress/locking/.gitignore0
-rw-r--r--fnet/src/tests/regress/scheduling/.gitignore0
-rw-r--r--fnet/src/tests/regress/spiral/.gitignore0
-rw-r--r--fnet/src/tests/regress/sync_execute/.gitignore0
-rw-r--r--fnet/src/tests/regress/thread_id/.gitignore0
-rw-r--r--fnet/src/tests/regress/time/.gitignore0
-rw-r--r--fnet/src/tests/scheduling/.gitignore6
-rw-r--r--fnet/src/tests/scheduling/CMakeLists.txt15
-rw-r--r--fnet/src/tests/scheduling/DESC1
-rw-r--r--fnet/src/tests/scheduling/FILES1
-rw-r--r--fnet/src/tests/scheduling/schedule.cpp153
-rw-r--r--fnet/src/tests/scheduling/sloweventloop.cpp65
-rw-r--r--fnet/src/tests/sync_execute/.gitignore4
-rw-r--r--fnet/src/tests/sync_execute/CMakeLists.txt8
-rw-r--r--fnet/src/tests/sync_execute/DESC1
-rw-r--r--fnet/src/tests/sync_execute/FILES1
-rw-r--r--fnet/src/tests/sync_execute/sync_execute.cpp39
-rw-r--r--fnet/src/tests/thread_selection/.gitignore1
-rw-r--r--fnet/src/tests/thread_selection/CMakeLists.txt8
-rw-r--r--fnet/src/tests/thread_selection/thread_selection_test.cpp89
-rw-r--r--fnet/src/tests/time/.gitignore4
-rw-r--r--fnet/src/tests/time/CMakeLists.txt8
-rw-r--r--fnet/src/tests/time/DESC1
-rw-r--r--fnet/src/tests/time/FILES1
-rw-r--r--fnet/src/tests/time/timespeed.cpp68
-rw-r--r--fnet/src/vespa/fnet/.gitignore6
-rw-r--r--fnet/src/vespa/fnet/CMakeLists.txt29
-rw-r--r--fnet/src/vespa/fnet/channel.cpp40
-rw-r--r--fnet/src/vespa/fnet/channel.h95
-rw-r--r--fnet/src/vespa/fnet/channellookup.cpp66
-rw-r--r--fnet/src/vespa/fnet/channellookup.h91
-rw-r--r--fnet/src/vespa/fnet/config.cpp22
-rw-r--r--fnet/src/vespa/fnet/config.h24
-rw-r--r--fnet/src/vespa/fnet/connect_thread.cpp57
-rw-r--r--fnet/src/vespa/fnet/connect_thread.h47
-rw-r--r--fnet/src/vespa/fnet/connection.cpp699
-rw-r--r--fnet/src/vespa/fnet/connection.h509
-rw-r--r--fnet/src/vespa/fnet/connector.cpp101
-rw-r--r--fnet/src/vespa/fnet/connector.h85
-rw-r--r--fnet/src/vespa/fnet/context.h45
-rw-r--r--fnet/src/vespa/fnet/controlpacket.cpp110
-rw-r--r--fnet/src/vespa/fnet/controlpacket.h101
-rw-r--r--fnet/src/vespa/fnet/databuffer.cpp139
-rw-r--r--fnet/src/vespa/fnet/databuffer.h663
-rw-r--r--fnet/src/vespa/fnet/dummypacket.cpp52
-rw-r--r--fnet/src/vespa/fnet/dummypacket.h56
-rw-r--r--fnet/src/vespa/fnet/fdselector.cpp104
-rw-r--r--fnet/src/vespa/fnet/fdselector.h215
-rw-r--r--fnet/src/vespa/fnet/fnet.h142
-rw-r--r--fnet/src/vespa/fnet/frt/.gitignore3
-rw-r--r--fnet/src/vespa/fnet/frt/CMakeLists.txt14
-rw-r--r--fnet/src/vespa/fnet/frt/error.cpp61
-rw-r--r--fnet/src/vespa/fnet/frt/error.h25
-rw-r--r--fnet/src/vespa/fnet/frt/frt.h37
-rw-r--r--fnet/src/vespa/fnet/frt/invokable.h14
-rw-r--r--fnet/src/vespa/fnet/frt/invoker.cpp189
-rw-r--r--fnet/src/vespa/fnet/frt/invoker.h168
-rw-r--r--fnet/src/vespa/fnet/frt/isharedblob.h17
-rw-r--r--fnet/src/vespa/fnet/frt/memorytub.cpp42
-rw-r--r--fnet/src/vespa/fnet/frt/memorytub.h147
-rw-r--r--fnet/src/vespa/fnet/frt/packets.cpp271
-rw-r--r--fnet/src/vespa/fnet/frt/packets.h102
-rw-r--r--fnet/src/vespa/fnet/frt/reflection.cpp198
-rw-r--r--fnet/src/vespa/fnet/frt/reflection.h160
-rw-r--r--fnet/src/vespa/fnet/frt/rpcrequest.cpp119
-rw-r--r--fnet/src/vespa/fnet/frt/rpcrequest.h193
-rw-r--r--fnet/src/vespa/fnet/frt/supervisor.cpp516
-rw-r--r--fnet/src/vespa/fnet/frt/supervisor.h137
-rw-r--r--fnet/src/vespa/fnet/frt/target.cpp14
-rw-r--r--fnet/src/vespa/fnet/frt/target.h56
-rw-r--r--fnet/src/vespa/fnet/frt/values.cpp1473
-rw-r--r--fnet/src/vespa/fnet/frt/values.h535
-rw-r--r--fnet/src/vespa/fnet/iexecutable.h23
-rw-r--r--fnet/src/vespa/fnet/info.cpp88
-rw-r--r--fnet/src/vespa/fnet/info.h61
-rw-r--r--fnet/src/vespa/fnet/iocomponent.cpp125
-rw-r--r--fnet/src/vespa/fnet/iocomponent.h324
-rw-r--r--fnet/src/vespa/fnet/ipacketfactory.h34
-rw-r--r--fnet/src/vespa/fnet/ipackethandler.h55
-rw-r--r--fnet/src/vespa/fnet/ipacketstreamer.h80
-rw-r--r--fnet/src/vespa/fnet/iserveradapter.h59
-rw-r--r--fnet/src/vespa/fnet/packet.cpp16
-rw-r--r--fnet/src/vespa/fnet/packet.h156
-rw-r--r--fnet/src/vespa/fnet/packetqueue.cpp276
-rw-r--r--fnet/src/vespa/fnet/packetqueue.h284
-rw-r--r--fnet/src/vespa/fnet/scheduler.cpp175
-rw-r--r--fnet/src/vespa/fnet/scheduler.h248
-rw-r--r--fnet/src/vespa/fnet/signalshutdown.cpp25
-rw-r--r--fnet/src/vespa/fnet/signalshutdown.h25
-rw-r--r--fnet/src/vespa/fnet/simplepacketstreamer.cpp67
-rw-r--r--fnet/src/vespa/fnet/simplepacketstreamer.h40
-rw-r--r--fnet/src/vespa/fnet/spiral/.gitignore0
-rw-r--r--fnet/src/vespa/fnet/stats.cpp98
-rw-r--r--fnet/src/vespa/fnet/stats.h101
-rw-r--r--fnet/src/vespa/fnet/task.cpp54
-rw-r--r--fnet/src/vespa/fnet/task.h75
-rw-r--r--fnet/src/vespa/fnet/testkit/xsync/.gitignore0
-rw-r--r--fnet/src/vespa/fnet/transport.cpp163
-rw-r--r--fnet/src/vespa/fnet/transport.h291
-rw-r--r--fnet/src/vespa/fnet/transport_thread.cpp733
-rw-r--r--fnet/src/vespa/fnet/transport_thread.h614
-rw-r--r--fnet/src/vespa/fnet/vtag.cpp21
-rw-r--r--fnet/src/vespa/fnet/vtag.h13
-rw-r--r--fnet/testrun/.gitignore10
213 files changed, 20792 insertions, 0 deletions
diff --git a/fnet/.gitignore b/fnet/.gitignore
new file mode 100644
index 00000000000..1ec479be9bc
--- /dev/null
+++ b/fnet/.gitignore
@@ -0,0 +1,10 @@
+.Build_completed
+.Dist_completed
+.Install_completed
+.PreBuild_completed
+doc
+include
+lib
+update.log
+Makefile
+Testing
diff --git a/fnet/CMakeLists.txt b/fnet/CMakeLists.txt
new file mode 100644
index 00000000000..edf99050c43
--- /dev/null
+++ b/fnet/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_define_module(
+ DEPENDS
+ fastos
+ vespalog
+ vespalib
+
+ LIBS
+ src/vespa/fnet
+ src/vespa/fnet/frt
+
+ TESTS
+ src/examples/frt/rpc
+ src/examples/ping
+ src/examples/proxy
+ src/examples/timeout
+ src/tests/connect_thread
+ src/tests/connection_spread
+ src/tests/databuffer
+ src/tests/examples
+ src/tests/fdselector
+ src/tests/frt/memorytub
+ src/tests/frt/method_pt
+ src/tests/frt/parallel_rpc
+ src/tests/frt/rpc
+ src/tests/frt/values
+ src/tests/info
+ src/tests/locking
+ src/tests/printstuff
+ src/tests/scheduling
+ src/tests/sync_execute
+ src/tests/thread_selection
+ src/tests/time
+)
diff --git a/fnet/INSTALL b/fnet/INSTALL
new file mode 100644
index 00000000000..9bb6c6152a4
--- /dev/null
+++ b/fnet/INSTALL
@@ -0,0 +1,22 @@
+*********************************
+* Compiling and Installing fnet *
+*********************************
+
+'fnet' uses 'FastOS'.
+In the following instructions, let %FASTOS_DIR% denote the install
+directory for FastOS. A reasonable install directory would be:
+ %FASTOS_DIR% = '/usr/fastsearch/fastos'
+
+Install FastOS:
+- checkout the fastos CVS module
+- go to fastos/src/fastos
+- ./configure --install-dir %FASTOS_DIR% [<config parameters>]
+ (run ./configure --help for help)
+- make install
+
+Install fnet:
+- checkout the fnet CVS module
+- go to fnet/src
+- ./configure --fastos-dir %FASTOS_DIR% [<config parameters>]
+ (run ./configure --fastos-dir %FASTOS_DIR% --help for help)
+- make install
diff --git a/fnet/OWNERS b/fnet/OWNERS
new file mode 100644
index 00000000000..0aa3eee0364
--- /dev/null
+++ b/fnet/OWNERS
@@ -0,0 +1,2 @@
+havardpe
+balder
diff --git a/fnet/README b/fnet/README
new file mode 100644
index 00000000000..dab36e5b120
--- /dev/null
+++ b/fnet/README
@@ -0,0 +1,26 @@
+This cvs module contains the code for FNET and FRT
+FNET currently stands for 'FuNET'
+FRT currently stands for 'FNET Remote Tools'
+
+FNET is a multi-threaded object-oriented networking library based on
+FastOS. FRT implements a proprietary RPC protocol on top of FNET.
+
+For how to compile and install, read the INSTALL file.
+For release information, read the RELEASEINFO file.
+
+The maintainer of this module is [havardpe@yahoo-inc.com]
+
+Notes from the maintainer:
+
+This library is work in progress. This means that some APIs may change
+in new versions. I will try to keep these changes to a minimum and
+also document them in the RELEASEINFO file.
+
+No extra effort has been added to hide things from the users of this
+library. This means that you can use components like the scheduler to
+implement a scheduler thread that has nothing to do with
+networking. It also means that you have to expect to handle more API
+changes if you are using classes not intended for direct use. It also
+means that you will have access to methods you were never meant to
+invoke. If this becomes a problem, I will start making things private
+and the classes more 'friendly'.
diff --git a/fnet/RELEASEINFO b/fnet/RELEASEINFO
new file mode 100644
index 00000000000..c4a53d9f1dc
--- /dev/null
+++ b/fnet/RELEASEINFO
@@ -0,0 +1,788 @@
+=======================================================================
+ FNET CURRENT (2006-04-24)
+.......................................................................
+
+
+Misc
+----
+
+- (2007-01-12) Make enabling/disabling the direct write optimization
+ runtime rather than compile time.
+
+
+Bugfixes
+--------
+
+- (2007-01-09) Don't open new channels unless the channel id was
+ invented by our peer. Also, don't send a reply when an rpc request
+ error occurs. (avoid infinite error response loop)
+
+- (2006-09-21) Handle BAD_REQUEST/NULL method name case. When a bad
+ request was handled, the method name (NULL) would cause the method
+ lookup to crash. This should be resolved now.
+
+- On connection close: discard packets from the inner packet queue
+ even if the outer packet queue is empty. Not doing this could
+ trigger a resource leak if a dummy packet responsible for cleanup
+ (and channel closing) was left in the inner queue after the
+ connection was closed.
+
+
+=======================================================================
+ FNET V_2_0_0_RELEASE
+.......................................................................
+
+
+New Dependencies
+----------------
+
+- vespalib (for test framework)
+
+
+Removed Code/Features
+---------------------
+
+- Moved the core test framework into vespalib. FNET now depends on
+ vespalib for this. The FNET test framework is now implemented on top
+ of the test framework supplied by vespalib. Also, the vespalib test
+ script (testrun.sh) is used to control the running of FNET tests.
+
+- Removed FNET_LogHandler class. This removes the simple logging
+ framework located in FNET. It was deprecated several versions ago,
+ and all internal logging in FNET is now done using the new log
+ module.
+
+- Removed OO RPC Support (RPC with support for objects and
+ classes). This is done to simplify the overall design of RPC, which
+ also makes it simpler to implement compatible components in other
+ languages. RPC packet codes and error codes have been re-enumerated
+ to cover up the holes left by the removed support for OO RPC. The
+ protocol documentation has been modified to reflect the new RPC
+ design. NB: this makes RPC incompatible with older versions of FNET.
+
+
+Spiral
+------
+
+Spiral is a simplified single-threaded RPC abstraction layer. It is
+intended for truly single-threaded applications that does not want to
+mess around with the complete FNET API. Spiral currently only supports
+the RPC client aspect.
+
+
+New Features
+------------
+
+- Added support for limiting the size of input/output buffers for
+ connections. This is done by invoking the
+ FNET_Transport::SetMaxInputBufferSize and
+ FNET_Transport::SetMaxOutputBufferSize methods.
+
+- Added FNET_Connection::GetQueueLen method that may be used to obtain the
+ number of packets currently located in the packet output queue.
+
+- Added support for enable/disable read and disable write events for
+ io components. This is used by the support to select on external
+ file descriptors.
+
+- Added support to shrink databuffers. This is used by the support to
+ limit input/output buffers.
+
+- Added support for selecting on external file descriptors by using
+ the FNET IO component framework. This includes using the
+ FNET_FDSelector class and the FNET_IFDSelectorHandler interface.
+
+- Better support for single-threaded compile. The test system has
+ been modified to work when compiled single-threaded. A new test
+ macro named TEST_THREADS may be used by tests that need threads to
+ run. This will result in the test begin skipped if threads are not
+ supported.
+
+- Added FRT_ISharedBlob interface with corresponding support. This may
+ be used to share the same memory regions between multiple
+ requests. Also, all shared blobs will be subref'ed by the request
+ as soon as the request packet is encoded to the connection output
+ buffer.
+
+
+Bugfixes
+--------
+
+- Fixed a minor bug in the xsync daemon; close other end of pipe after
+ fork, to avoid hang in mother process if child fails.
+
+
+=======================================================================
+ FNET V_1_4_9_RELEASE
+.......................................................................
+
+
+Robustness
+----------
+
+- Make task scheduling more robust in cases where the event loop
+ latency is very high. (ref Bug #462022)
+
+
+=======================================================================
+ FNET V_1_4_8_RELEASE
+.......................................................................
+
+
+=======================================================================
+ FNET V_1_4_7_RELEASE
+.......................................................................
+
+
+Misc
+----
+
+- Minor timing adjustments in the RPC invoke test to make it work
+ better.
+
+
+=======================================================================
+ FNET V_1_4_6_RELEASE
+.......................................................................
+
+
+=======================================================================
+ FNET V_1_4_5_RELEASE
+.......................................................................
+
+
+=======================================================================
+ FNET V_1_4_4_RELEASE
+.......................................................................
+
+
+Misc
+----
+
+- avoid some warnings with gcc 4 (weffc++)
+
+
+Bugfixes
+--------
+
+- check if _adminChannel is closed while waiting for callbacks
+ (in the CloseAdminChannel method)
+
+
+=======================================================================
+ FNET V_1_4_3_RELEASE
+.......................................................................
+
+
+Misc
+----
+
+- use vtag stuff in fastos to report fnet version
+ (to avoid empty release hassle)
+
+- Better support for make -j
+
+
+=======================================================================
+ FNET V_1_4_2_RELEASE
+.......................................................................
+
+
+[NB: no changes at all; still identifies as 1.4.1]
+
+
+=======================================================================
+ FNET V_1_4_1_RELEASE
+.......................................................................
+
+
+=======================================================================
+ FNET V_1_4_0_RELEASE
+.......................................................................
+
+
+Public testing toolkit
+----------------------
+
+The FNET testing toolkit has been made public and is now a part of the
+fnet library. The macros have been renamed to all start with
+'TEST_'. The local fnet tests have been modified and an example test
+program has been added.
+
+
+New logging API
+---------------
+
+All logging has been converted to use the new "log" module
+and its API. All functions in class FNET_LogHandler are now
+deprecated, in particular FNET_LogHandler::SetLogMask and
+FNET_LogHandler::SetLogHandler no longer have any effect.
+All users of fnet will need to link with the "log" library.
+
+
+Misc
+----
+
+- thread_id test is void'ed if fnet is compiled without threads.
+
+
+=======================================================================
+ FNET V_1_3_0_RELEASE
+.......................................................................
+
+
+FRT/RPC Lobotomy/Cleanup
+------------------------
+
+The support for local invocation (and location transparency) is
+removed. The code is also somewhat simplified to reflect the now less
+general nature of things. It hurts to see so much thought go down the
+drain, but it greatly reduces maintenance complexity and simplifies
+future development. Who needs sub microsecond invocations anyway...
+
+Changed code to avoid warning messages produced by gcc 3.3 and later
+when compiling with the -Weffc++ option.
+
+
+Removed Code
+------------
+
+- FNET_ITransportHook
+- FNET_IPingable
+- FNET_Pinger
+- FNET_ServerInfo
+- FNET_Transport::SetMinEventTimeOut
+- FNET_Transport::SetPingInterval
+- FNET_Transport::AddPingTarget
+- FNET_Transport::RemovePingTarget
+- FNET_Transport::HookNow
+
+
+New Features
+------------
+
+- Added preliminary support for 2-way RPC.
+
+- Added support for invoking RPC methods without having an FRT_Target.
+
+- Added a Reset() method to the reflection manager that removes all
+ methods.
+
+- Added a method mismatch rpc hook that may be used to catch method
+ calls not matching any methods.
+
+- Added support for rpc session state (All rpc calls made on a single
+ connection is referred to as a session). This was done by adding a
+ context to each connection (used to store the session state), making
+ it possible to obtain the underlying connection given
+ the rpc request object and adding special rpc hooks that are called
+ when a connection is established, when a connection is lost and
+ right before a connection is deleted.
+
+- The transport object now comes with a built-in scheduler object.
+
+- The FNET_Scheduler constructor may be given the current time. This
+ simplifies running the scheduler in a simulated environment.
+
+- A Sync method has been added to the connection class. This method
+ may be used to block until all previously posted packets have been
+ encoded.
+
+- Added InTub method to the memorytub class. This method may be used
+ to check if the tub owns a given byte of memory. This feature is
+ primarily used for regression testing.
+
+- The FNET_Connector class has been given a GetPortNumber method to be
+ able to extract the port number of the underlying server socket.
+
+- Allow listening on port number 0.
+
+- Add FRT_Target::GetConnection() method. Only returns a pointer to
+ the connection owned by the target. No new references are allocated.
+
+
+API changes
+-----------
+
+- The FRT supervisor may be constructed either in stand-alone mode, or
+ based on an external transport object and an external thread
+ pool. The transport proxy methods may only be used when in
+ stand-alone mode.
+
+- The scheduler no longer implements the transport hook interface
+ (removed). The virtual Tick method has been replaced by the
+ non-virtual CheckTasks method.
+
+- The transport Main/Start methods no longer take a transport hook as
+ parameter (the interface has been removed). Similar functionality
+ may now be obtained by using the transport InitEventLoop and
+ EventLoopIteration methods.
+
+- The RPC target InvokeAsync method now returns bool, true means that
+ the method completed immediately. false means the invocation is in
+ progress.
+
+- The FRT_IRequestWait::RequestDone method now takes a second
+ parameter; a bool indicating whether this invocation is done
+ synchronously (directly from the invoke method).
+
+
+Internal changes
+----------------
+
+- Added IOC CleanupHook method that is called right before an IOC is
+ deleted. This is used by the FNET_Connection class to support a
+ connection cleanup callback which in turn is used by the FRT_Supervisor to
+ activate the SessionFini hook needed for RPC session support.
+
+- Statistics updating is now done with a scheduled stats update
+ task. This reduces the complexity of the event loop.
+
+- The connection write method now avoids writing 0 bytes to the
+ network. Also, empty writes are silently ignored since using the
+ packet queue for control packets is perfectly legal.
+
+- Channel IDs are now chosen in a way that is future compatible with
+ 2-way channel creation.
+
+
+Misc
+----
+
+- Store listen/connect spec in IOC and use it in IOC related log
+ messages.
+
+- Added rpc proxy example program that may be useful for debugging.
+
+- Added FNET_DummyPacket helper packet class that may be used to
+ implement hooks in the connection output packet queue.
+
+- The FNET_Packet::NoPCODE() and FNET_Connection::NoID() methods have
+ been replaced by the FNET_NOID macro.
+
+- Added FNET_ prefix to DISABLE_DIRECT_WRITE compile flag.
+
+- Added FNET_SANITY_CHECKS compile flag. This enables some extra
+ sanity checks in the event loop.
+
+- Added generic cross-host testing support as part of testing
+ framework (xtest make target removed). This includes the xsync
+ daemon used to sync test states between hosts. The environment
+ variables PORTBASE and PEER are used to control testing behavior.
+
+- In regression test framework: enable output from passed test cases
+ to be collapsed to reduce logging.
+
+- Added support for mutex instrumentation. This requires a version of
+ FastOS supporting instrumented mutexes.
+
+- Added FRT_Supervisor::GetListenPort() method.
+
+
+Bugfixes
+--------
+
+- RPC: Handle request return (after request detach) while still
+ inside sync invocation method correctly (also added to regress
+ test).
+
+- RPC: ensure that the cleanup handler is run also when the request is
+ recycled by the user without making the trip back to the request
+ pool.
+
+
+=======================================================================
+ FNET V_1_2_4_RELEASE
+.......................................................................
+
+
+Bugfixes
+--------
+
+- Wait for direct write to complete before discarding packets when the
+ connection is going down.
+
+
+=======================================================================
+ FNET V_1_2_3_RELEASE
+.......................................................................
+
+
+New Features
+------------
+
+- Added support for sending multiple packets at the same time. This
+ may reduce TCP latency when used with Nagle's algorithm.
+
+
+=======================================================================
+ FNET V_1_2_2_RELEASE
+.......................................................................
+
+
+Bugfixes
+--------
+
+- Fix Makefile to work with parallel compilation (gcc -j option).
+
+
+=======================================================================
+ FNET V_1_2_1_RELEASE
+.......................................................................
+
+
+Misc
+----
+
+- Max events per event loop iteration: 256 -> 4096
+
+
+Bugfixes
+--------
+
+- Avoid overlap between chunk header and chunk data in FRT memorytub.
+
+- Remove race around single req wait where condition could be
+ destructed while being locked.
+
+- Don't set connection state to closed if connect fails
+ synchronously. This caused a channel lost event to be sent on the
+ admin channel that caused a deadlock in the serverinfo class. It is
+ also an API improvement, since connect now fails either
+ synchronously or asynchronously (not both).
+
+
+=======================================================================
+ FNET V_1_2_0_RELEASE
+.......................................................................
+
+
+New Features
+------------
+
+- Added support to construct an FNET_DataBuffer based on a
+ preallocated buffer.
+
+- Added support for scheduling a task to be run as soon as possible.
+
+- Added support for killing a task so that it may not be scheduled in
+ the future.
+
+- Added databuffer methods to write data without checking for free
+ space. These methods have a 'Fast' suffix.
+
+- Added AssertValid method that may be called anytime to assert that
+ the databuffer is in a legal state.
+
+- Added endian detection. Use the FNET_Info::GetEndian method to
+ obtain host endian.
+
+- Added methods to read/peek information stored in reverse internet
+ order (little endian) in a databuffer.
+
+- VPATH compilation.
+
+
+Internal Changes
+----------------
+
+- Removed asserts used to check for valid data in databuffer read
+ methods.
+
+- When posting a packet, try to write it to the network directly (if
+ there is no other pending write operations). Post
+ an enable write event to the transport thread if there is pending
+ work after a direct write has been attempted. This behavior may be
+ disabled by using the --disable-direct-write configure option.
+
+
+Misc
+----
+
+- Added static instance of FNET_CMD_NOCOMMAND control packet for
+ completeness.
+
+- Moved fastserver stuff to the fastserver4 cvs module.
+
+- Simple regress test framework. Run tests with 'make test' from
+ fnet/src. Use 'make xtest' to run cross-host testing. This requires
+ setting the PORT and PEER environment variables to sane
+ value. Example: to run a cross-host test between host A and host B
+ using port 8000 (on both hosts); run 'make PORT=8000 PEER=B xtest'
+ on host A and 'make PORT=8000 PEER=A xtest' on host B.
+
+
+Bugfixes
+--------
+
+- Set connection state correctly if socket connect completes
+ synchronously.
+
+
+FRT ([F]NET [R]emote [T]ools) [middle-ware library]
+---------------------------------------------------
+
+The FRT library is a layer on top of FNET supporting a proprietary RPC
+protocol. In addition to the network protocol, the FRT library also
+contains API support for location transparent dynamic method
+invocation (examples and regress tests also included).
+
+
+=======================================================================
+ FNET V_1_1_2_RELEASE
+.......................................................................
+
+
+Bugfixes
+--------
+
+- Handle EOL returned from socket read. This lets FNET handle peers
+ that close connections nicely. Previously this would result in a
+ very tight event loop reading EOL from the socket in each iteration.
+
+- Avoid time measurement errors in scheduler by using absolute values
+ for bucket timeouts rather than accumulated delta values. If the
+ scheduler was invoked very often, it would appear that time stood
+ still.
+
+
+=======================================================================
+ FNET V_1_1_1_RELEASE
+.......................................................................
+
+
+Compiler nitpicks
+-----------------
+
+- Renamed TICK to SLOT_TICK in scheduler class to avoid conflict with
+ macro on Solaris.
+- Removed comma at end of enums in fs4 example packet library.
+- Use public inheritance in fsearch_maxthreads fs4 example
+ application.
+- Conceal infinite loop in fsearch_somethreads fs4 example
+ application.
+- Cast error-code to signed value before applying negation in
+ fs4 searchtest example application.
+- Cast enum to uint32_t to avoid constructor confusion in proxy
+ example application.
+- Explicitly cast stat values to float.
+
+
+Misc
+----
+
+- Removed memory leak in scheduling test to avoid HUGE memory leak
+ report on application exit.
+
+
+=======================================================================
+ FNET V_1_1_0_RELEASE
+.......................................................................
+
+
+API Changes
+-----------
+
+Added method bool FNET_IServerAdapter::InitAdminChannel(FNET_Channel
+*) That must be implemented by server applications using FNET. The
+method is called when a new incoming connection has been established
+in order to let the application keep track of its current
+clients. This channel works just like normal channels. The only
+difference is that it is opened before any packets are received,
+thereby allowing the application to implement connection-oriented
+stuff like proxies. Implementing this method to always return false
+will preserve old behavior. NOTE: closing the connection directly (by
+invoking Close()) is allowed from the InitAdminChannel method (This is
+an exception from the general rule that the application should close
+all connections with async events. The reason for this exception is to
+allow better resource management).
+
+Added an FNET_Context parameter to the FNET_Connection constructor and
+to the FNET_Transport::Connect method. This was done to enable the
+application to set the admin channel context in the client end-point
+of a connection. Also, VOIDP=NULL is used as default context, instead
+of the previously used INT=0xffffffff (FNET_Connection::NoID()).
+
+Added a parameter of type FNET_ITransportHook to the
+FNET_Transport::Start method in order to enable sharing the transport
+thread without the need to create a wrapper object (The parameter is
+optional with NULL as default value).
+
+Made the FNET_ITransportHook parameter to the FNET_Transport::Main
+method optional (default value is NULL).
+
+Server info objects may now be marked as 'bad'
+[MarkBad()/ClearBad()/IsBad()]. If a server (info) is marked as bad,
+FNET will try to keep it down, rather than up. Note that node badness
+may be set by the application code in any thread and is not protected
+by any locks. The most sensible use of this flag would be to use it as
+an async signal, raising it if a server goes bad, and never lowering
+it again (ClearBad() is implemented for completeness, but needs to be
+used with care).
+
+Added new method FNET_Channel::CloseAndFree() that may be used to
+close and free a channel in a single operation. This is equivalent
+with first invoking Close(), then Free(), but is more efficient.
+
+The packet streamer interface has been modified in order to support
+context-dependent un-streaming of packets and variable length packet
+headers. This has resulted in the following method changes:
+
+-----
+bool
+FNET_IServerAdapter::InitChannel(FNET_Channel *channel,
+ FNET_Packet *packet)
+-----
+||
+\/
+-----
+bool
+FNET_IServerAdapter::InitChannel(FNET_Channel *channel,
+ uint32_t pcode)
+-----
+
+This reflects the fact that in order to do context-dependent packet
+un-streaming, we need to resolve the context of the channel that
+should receive the packet before un-streaming the packet. This also
+means that the packet itself is not available when opening server-side
+channels.
+
+-----
+FNET_Packet *
+FNET_IPacketFactory::CreatePacket(uint32_t pcode)
+-----
+||
+\/
+-----
+FNET_Packet *
+FNET_IPacketFactory::CreatePacket(uint32_t pcode, FNET_Context context)
+-----
+
+The context is propagated to the packet factory in order to eliminate
+the need to change the FNET_Packet::Decode(...) method.
+
+-----
+uint32_t
+FNET_IPacketStreamer::GetHeaderLen()
+
+uint32_t
+FNET_IPacketStreamer::GetPacketLen(FNET_DataBuffer *src)
+-----
+||
+\/
+-----
+bool
+FNET_IPacketStreamer::GetPacketInfo(FNET_DataBuffer *src,
+ uint32_t *plen,
+ uint32_t *pcode,
+ uint32_t *chid,
+ bool *broken)
+-----
+
+The packet info is extracted before the packet is un-streamed in order
+to locate the channel that should receive the packet. This change
+gives us support for variable length packet headers and a way to
+signal that the connection is broken.
+
+-----
+FNET_Packet *
+FNET_IPacketStreamer::Decode(FNET_DataBuffer *src, uint32_t *chid)
+-----
+||
+\/
+-----
+FNET_Packet *
+FNET_IPacketStreamer::Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t pcode, FNET_Context context)
+-----
+
+The packet info is injected as parameters to avoid having to extract
+it from the packet a second time. Note that the chid has been replaced
+by the context of the channel that will receive the packet. This
+completes the support for context-dependent packet un-streaming.
+
+
+Internal Changes
+----------------
+
+Allow IOC_DELETE event before IOC_ADD event. This enables the
+application to post an event indicating that the connection itself
+should be closed when invoked to open the admin channel on that
+connection (only applies to server connections).
+
+Optimized FNET_DataBuffer by using pointers to define dead/data/free
+sections of the buffer.
+
+Each connection now has its own FNET_ChannelPool object instead of
+sharing an FNET_ObjectPool. This reduces locking.
+
+The methods on FNET_Channel has been un-inlined in order to
+instantiate FNET_Connection objects with an inlined FNET_ChannelPool
+object which in turn has inline FNET_Channel objects.
+
+Make sure that the packets posted on a connection are freed in the
+same order as they were posted, even if the connection goes down. This
+is done to enable the application to use packet free order for syncing
+purposes. It also enables the usage of packets that perform code in
+the free method that requires that all previously posted packets have
+already been freed (Like memory pool cleanup code).
+
+Changed some log messages to FNET_LOG_DEBUG.
+
+
+New Control Packets
+-------------------
+
+- FNET_CMD_TIMEOUT : This control packet command may be used to signal
+ a timeout. Note that FNET does not use timeout
+ packets internally. They are only included to
+ ease the implementation of timeout signaling in
+ applications using FNET.
+
+- FNET_CMD_BAD_PACKET : This control packet signals the reception of
+ a packet that could not be un-streamed. It
+ will be delivered in place of the packet that
+ was invalid.
+
+
+New Classes
+-----------
+
+- FNET_Task (schedulable tasks; timeouts etc.)
+- FNET_Scheduler (class for scheduling tasks)
+- FNET_ChannelPool (per-connection channel pooling)
+
+
+Removed Classes
+---------------
+
+- FNET_ObjectPool
+
+
+New Examples
+------------
+
+- proxy : A simple proxy implementation showing how FNET may be used
+ to handle raw data as packets and also how to focus on
+ connections rather than channels.
+
+- timeout : Simple application showing how to use the
+ task/scheduler classes to implement timeout support.
+
+
+Bugfixes
+--------
+
+- use maxreconnectdelay if the connect failed synchronously.
+
+- don't try to connect to target host if basic socket init fails.
+
+
+=======================================================================
+ FNET V_1_0_0_RELEASE
+.......................................................................
+
+
+Initial release.
+
+NOTE: 1.0.x is no longer supported.
diff --git a/fnet/build/buildspec.xml b/fnet/build/buildspec.xml
new file mode 100644
index 00000000000..adc792e0692
--- /dev/null
+++ b/fnet/build/buildspec.xml
@@ -0,0 +1,39 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<BuildSpecification>
+ <Owner>
+ <OwnerName></OwnerName>
+ <OwnerEmail></OwnerEmail>
+ </Owner>
+
+ <Dependencies os="all" arch="all">
+ <dep package="common/fastos" version="0.0" />
+ </Dependencies>
+
+ <PreBuild os="all" arch="all">
+ <configure path="src">
+ <parameter value="--fastos-dir ${fbuild_install_dir}/fastos" />
+ <parameter value="--install-dir ${fbuild_install_dir}/fnet" />
+ <if feature="nodirectwrite">
+ <parameter value="--disable-direct-write" />
+ </if>
+ </configure>
+ </PreBuild>
+
+ <Build os="all" arch="all">
+ <make path="src" target="bootstrap" />
+ </Build>
+
+ <PostBuild os="all" arch="all">
+ </PostBuild>
+
+ <Test os="all" arch="all">
+ </Test>
+
+ <Install os="all" arch="all">
+ <make path="src" target="install" />
+ </Install>
+
+ <Dist os="all" arch="all">
+ </Dist>
+
+</BuildSpecification>
diff --git a/fnet/ethereal/Makefile.am b/fnet/ethereal/Makefile.am
new file mode 100644
index 00000000000..7f169f25afa
--- /dev/null
+++ b/fnet/ethereal/Makefile.am
@@ -0,0 +1,26 @@
+# Makefile.am
+# Automake file for plugin
+
+INCLUDES = -I$(top_srcdir)
+
+plugindir = @plugindir@
+
+plugin_LTLIBRARIES = fnetrpc.la
+fnetrpc_la_SOURCES = packet-fnetrpc.c moduleinfo.h
+fnetrpc_la_LDFLAGS = -module -avoid-version
+fnetrpc_la_LIBADD = @PLUGIN_LIBS@
+
+# Libs must be cleared, or else libtool won't create a shared module.
+# If your module needs to be linked against any particular libraries,
+# add them here.
+LIBS =
+
+CLEANFILES = \
+ fnetrpc \
+ *~
+
+MAINTAINERCLEANFILES = \
+ Makefile.in
+
+EXTRA_DIST = \
+ Makefile.nmake
diff --git a/fnet/ethereal/Makefile.nmake b/fnet/ethereal/Makefile.nmake
new file mode 100644
index 00000000000..c743e4c011b
--- /dev/null
+++ b/fnet/ethereal/Makefile.nmake
@@ -0,0 +1,27 @@
+include ..\..\config.nmake
+
+############### no need to modify below this line #########
+
+CFLAGS=/DHAVE_CONFIG_H /I../.. /I../../wiretap $(GLIB_CFLAGS) \
+ /I$(PCAP_DIR)\include -D_U_="" $(LOCAL_CFLAGS)
+
+LDFLAGS = /NOLOGO /INCREMENTAL:no /MACHINE:I386 $(LOCAL_LDFLAGS)
+
+!IFDEF ENABLE_LIBETHEREAL
+LINK_PLUGIN_WITH=..\..\epan\libethereal.lib
+CFLAGS=/DHAVE_WIN32_LIBETHEREAL_LIB /D_NEED_VAR_IMPORT_ $(CFLAGS)
+
+OBJECTS=packet-fnetrpc.obj
+
+fnetrpc.dll fnetrpc.exp fnetrpc.lib : $(OBJECTS) $(LINK_PLUGIN_WITH)
+ link -dll /out:fnetrpc.dll $(LDFLAGS) $(OBJECTS) $(LINK_PLUGIN_WITH) \
+ $(GLIB_LIBS)
+
+!ENDIF
+
+clean:
+ rm -f $(OBJECTS) fnetrpc.dll fnetrpc.exp fnetrpc.lib *.pdb
+
+distclean: clean
+
+maintainer-clean: distclean
diff --git a/fnet/ethereal/moduleinfo.h b/fnet/ethereal/moduleinfo.h
new file mode 100644
index 00000000000..de49ef89a67
--- /dev/null
+++ b/fnet/ethereal/moduleinfo.h
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* Included *after* config.h, in order to re-define these macros */
+
+#ifdef PACKAGE
+#undef PACKAGE
+#endif
+
+/* Name of package */
+#define PACKAGE "fnetrpc"
+
+
+#ifdef VERSION
+#undef VERSION
+#endif
+
+/* Version number of package */
+#define VERSION "0.0.1"
+
diff --git a/fnet/ethereal/packet-fnetrpc.c b/fnet/ethereal/packet-fnetrpc.c
new file mode 100644
index 00000000000..370b5e29e70
--- /dev/null
+++ b/fnet/ethereal/packet-fnetrpc.c
@@ -0,0 +1,389 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/* packet-fnetrpc.c */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <gmodule.h>
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <string.h>
+
+/* forward reference */
+void proto_register_fnetrpc();
+void proto_reg_handoff_fnetrpc();
+static void dissect_fnetrpc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
+
+static void decode_params_bigend(proto_tree *tree, tvbuff_t *tvb, int offset, int len);
+static void decode_params_litend(proto_tree *tree, tvbuff_t *tvb, int offset, int len);
+
+/* Define version if we are not building ethereal statically */
+#ifndef ENABLE_STATIC
+G_MODULE_EXPORT const gchar version[] = "0.0";
+#endif
+
+static int proto_fnetrpc = -1;
+static int global_fnetrpc_port = 10101;
+static dissector_handle_t fnetrpc_handle;
+
+#ifndef ENABLE_STATIC
+G_MODULE_EXPORT void
+plugin_register(void)
+{
+ /* register the new protocol, protocol fields, and subtrees */
+ if (proto_fnetrpc == -1) { /* execute protocol initialization only once */
+ proto_register_fnetrpc();
+ }
+}
+
+G_MODULE_EXPORT void
+plugin_reg_handoff(void){
+ proto_reg_handoff_fnetrpc();
+}
+#endif
+
+#define FRT_LITTLE_ENDIAN_FLAG 0x01
+#define FRT_NOREPLY_FLAG 0x02
+
+static int hf_fnetrpc_packet_type = -1;
+static int hf_fnetrpc_packet_flags = -1;
+static int hf_fnetrpc_packet_len = -1;
+static int hf_fnetrpc_packet_reqid = -1;
+static int hf_fnetrpc_req_method = -1;
+
+static int hf_fnetrpc_typestring = -1;
+static int hf_fnetrpc_val_int32 = -1;
+static int hf_fnetrpc_val_array = -1;
+static int hf_fnetrpc_val_string = -1;
+
+static int hf_fnetrpc_noreply_flag = -1;
+static int hf_fnetrpc_litend_flag = -1;
+
+static gint ett_fnetrpc = -1;
+static gint ett_fnetrpc_params = -1;
+static gint ett_fnetrpc_retval = -1;
+
+static const value_string packettypenames[] = {
+ { 100, "RPC Request" },
+ { 101, "RPC Reply" },
+ { 102, "RPC Error" },
+ { 0, NULL },
+};
+
+static hf_register_info hf[] = {
+ { &hf_fnetrpc_packet_len,
+ { "FRT Packet length", "fnetrpc.packetlen",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_packet_flags,
+ { "FRT Packet flags", "fnetrpc.packetflags",
+ FT_UINT16, BASE_HEX, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_packet_type,
+ { "FRT Packet type", "fnetrpc.packettype",
+ FT_UINT16, BASE_HEX, VALS(packettypenames), 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_noreply_flag,
+ { "FRT noreply flag", "fnetrpc.flags.noreply",
+ FT_BOOLEAN, 8, NULL, FRT_NOREPLY_FLAG,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_litend_flag,
+ { "FRT little-endian flag", "fnetrpc.flags.littleendian",
+ FT_BOOLEAN, 8, NULL, FRT_LITTLE_ENDIAN_FLAG,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_packet_reqid,
+ { "FRT Request id", "fnetrpc.requestid",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_req_method,
+ { "FRT name of called method", "fnetrpc.request.method",
+ FT_STRING, 0, NULL, 0x0,
+ "", HFILL }
+ },
+
+ { &hf_fnetrpc_typestring,
+ { "FRT value typestring", "fnetrpc.value.typestring",
+ FT_STRING, 0, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_val_int32,
+ { "FRT int32 value", "fnetrpc.value.int32",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_val_array,
+ { "FRT array length", "fnetrpc.value.array",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }
+ },
+ { &hf_fnetrpc_val_string,
+ { "FRT string value", "fnetrpc.value.string",
+ FT_STRING, 0, NULL, 0x0,
+ "", HFILL }
+ },
+
+};
+
+/* Setup protocol subtree array */
+static gint *ett[] = {
+ &ett_fnetrpc,
+ &ett_fnetrpc_params,
+ &ett_fnetrpc_retval,
+};
+
+
+void
+proto_register_fnetrpc(void)
+{
+ module_t *fnetrpc_module;
+
+ if (proto_fnetrpc == -1) {
+ proto_fnetrpc = proto_register_protocol (
+ "FNET Remote Tools Protocol", /* name */
+ "FRT", /* short name */
+ "frt" /* abbrev */
+ );
+ }
+ fnetrpc_module = prefs_register_protocol(proto_fnetrpc, proto_reg_handoff_fnetrpc);
+
+ proto_register_field_array(proto_fnetrpc, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+}
+
+
+void
+proto_reg_handoff_fnetrpc(void)
+{
+ static int Initialized=FALSE;
+
+ if (!Initialized) {
+ fnetrpc_handle = create_dissector_handle(dissect_fnetrpc, proto_fnetrpc);
+ dissector_add("tcp.port", global_fnetrpc_port, fnetrpc_handle);
+ }
+}
+
+static void
+decode_rpc(int packet_type, proto_tree *tree, tvbuff_t *tvb, int offset, int iplen)
+{
+ int islitend = 0;
+ proto_tree *my_tree = NULL;
+ proto_item *ti = proto_tree_add_item(tree, proto_fnetrpc, tvb, offset, iplen, FALSE);
+
+ proto_item_append_text(ti, ", %s", val_to_str(packet_type, packettypenames, "Unknown type (%d)"));
+
+ my_tree = proto_item_add_subtree(ti, ett_fnetrpc);
+
+ proto_tree_add_item(my_tree, hf_fnetrpc_packet_flags, tvb, offset, 2, FALSE);
+ proto_tree_add_item(my_tree, hf_fnetrpc_noreply_flag, tvb, offset+1, 1, FALSE);
+ proto_tree_add_item(my_tree, hf_fnetrpc_litend_flag, tvb, offset+1, 1, FALSE);
+ proto_tree_add_item(my_tree, hf_fnetrpc_packet_type, tvb, offset+2, 2, FALSE);
+ proto_tree_add_item(my_tree, hf_fnetrpc_packet_reqid, tvb, offset+4, 4, FALSE);
+
+ islitend = (tvb_get_guint8(tvb, offset+1) & FRT_LITTLE_ENDIAN_FLAG);
+
+ iplen -= 8; // consumed as headers
+ offset += 8;
+
+ // fprintf(stderr, "type %d / iplen %d\n", packet_type, iplen);
+ if (packet_type == 101) {
+ proto_tree *retval_tree = NULL;
+ retval_tree = proto_item_add_subtree(my_tree, ett_fnetrpc_retval);
+ if (islitend) {
+ decode_params_litend(retval_tree, tvb, offset, iplen);
+ } else {
+ decode_params_bigend(retval_tree, tvb, offset, iplen);
+ }
+ }
+
+ if (packet_type == 100) {
+ gint slen = 0;
+ int ssz = 0;
+ guint8 buf[256];
+
+ // 4 bytes integer for length of method name
+ if (iplen < 4) return;
+
+ if (islitend) {
+ ssz = tvb_get_letohl(tvb, offset);
+ } else {
+ ssz = tvb_get_ntohl(tvb, offset);
+ }
+ offset += 4;
+ iplen -= 4;
+
+ // fprintf(stderr, "ssz %d\n", ssz);
+
+ // ssz bytes of method name
+ if (iplen >= ssz) {
+ proto_tree *param_tree = NULL;
+
+ // max 255 bytes displayed
+ slen = tvb_get_nstringz0(tvb, offset, 1 + ( ssz > 255 ? 255 : ssz), buf);
+ proto_tree_add_string(my_tree, hf_fnetrpc_req_method, tvb, offset, ssz, buf);
+ offset += ssz;
+ iplen -= ssz;
+ // fprintf(stderr, "slen %d\n", slen);
+
+ proto_item_append_text(ti, ": %s()", buf);
+
+ param_tree = proto_item_add_subtree(my_tree, ett_fnetrpc_params);
+ if (islitend) {
+ decode_params_litend(param_tree, tvb, offset, iplen);
+ } else {
+ decode_params_bigend(param_tree, tvb, offset, iplen);
+ }
+ }
+ }
+}
+
+static void
+dissect_fnetrpc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int plen = tvb_length(tvb);
+ guint16 packet_type = 0;
+
+ /* if displaying protocol name, set to our name */
+ if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "FRT");
+ }
+ /* Clear out stuff in the info column */
+ if (check_col(pinfo->cinfo, COL_INFO)) {
+ col_clear(pinfo->cinfo,COL_INFO);
+ }
+
+ if (plen < 12) {
+ if (check_col(pinfo->cinfo, COL_INFO)) {
+ col_add_str(pinfo->cinfo, COL_INFO, "Too short packet");
+ }
+ return;
+ }
+
+ packet_type = tvb_get_ntohs(tvb, 6);
+
+ /* Clear out stuff in the info column */
+ if (check_col(pinfo->cinfo, COL_INFO)) {
+ col_clear(pinfo->cinfo,COL_INFO);
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s",
+ val_to_str(packet_type, packettypenames,
+ "Unknown type (%d)"));
+ }
+
+ if (tree) { /* being asked for details */
+ int offset = 0;
+
+ while (offset + 4 < plen) {
+ int iplen = tvb_get_ntohl(tvb, offset);
+ proto_item *itlen =
+ proto_tree_add_item(tree, hf_fnetrpc_packet_len, tvb, offset, 4, FALSE);
+
+ offset += 4;
+ if (offset + iplen > plen) {
+ proto_item_append_text(itlen, " (%d bytes missing)", offset + iplen - plen);
+ iplen = plen - offset;
+ }
+ if (iplen < 8)
+ break;
+
+ decode_rpc(packet_type, tree, tvb, offset, iplen);
+ // if (offset + iplen < plen) {
+ // fprintf(stderr, "decoded %d bytes at %d, of total %d\n", iplen, offset, plen);;
+ // }
+ offset += iplen;
+
+ }
+ if (plen > offset) {
+ proto_item *extra = proto_tree_add_item(tree, hf_fnetrpc_packet_len, tvb, offset, plen - offset, FALSE);
+ proto_item_append_text(extra, " (%d undecoded bytes)", plen - offset);
+ }
+ }
+}
+
+void
+decode_params_litend(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
+{
+ proto_item *tsit = NULL;
+ int i;
+ int ssz;
+ guint8 typestring[256];
+ guint8 buf[256];
+ gint slen = 0;
+
+ if (len < 4) return;
+
+ ssz = tvb_get_letohl(tvb, offset);
+
+ offset += 4;
+ len -= 4;
+ if (ssz > len) return;
+
+ slen = tvb_get_nstringz0(tvb, offset, 1 + ( ssz > 255 ? 255 : ssz), typestring);
+ tsit= proto_tree_add_string(tree, hf_fnetrpc_typestring, tvb, offset, ssz, typestring);
+
+ offset += ssz;
+ len -= ssz;
+
+ for (i = 0; i < (int)strlen(typestring); i++) {
+ int j = 0;
+ int narr = 0;
+
+ switch (typestring[i]) {
+
+ case 'i':
+ if (len < 4) return;
+ proto_tree_add_item(tree, hf_fnetrpc_val_int32, tvb, offset, 4, TRUE);
+ offset += 4;
+ len -= 4;
+ break;
+
+ case 's':
+ if (len < 4) return;
+ ssz = tvb_get_letohl(tvb, offset);
+ offset += 4;
+ len -= 4;
+ if (ssz > len) return;
+ slen = tvb_get_nstringz0(tvb, offset, 1 + ( ssz > 255 ? 255 : ssz), buf);
+ proto_tree_add_string(tree, hf_fnetrpc_val_string, tvb, offset, ssz, buf);
+ offset += ssz;
+ len -= ssz;
+ break;
+
+ case 'S':
+ if (len < 4) return;
+ narr = tvb_get_letohl(tvb, offset);
+ proto_tree_add_item(tree, hf_fnetrpc_val_array, tvb, offset, 4, TRUE);
+ offset += 4;
+ len -= 4;
+ for (j = 0; j < narr; j++) {
+ if (len < 4) return;
+ ssz = tvb_get_letohl(tvb, offset);
+ offset += 4;
+ len -= 4;
+ if (ssz > len) return;
+ slen = tvb_get_nstringz0(tvb, offset, 1 + ( ssz > 255 ? 255 : ssz), buf);
+ proto_tree_add_string(tree, hf_fnetrpc_val_string, tvb, offset, ssz, buf);
+ offset += ssz;
+ len -= ssz;
+ }
+ break;
+
+ default:
+ proto_item_append_text(tsit, " unknown value type '%c' (0x%02x)",
+ (int)typestring[i], (int)typestring[i]);
+ }
+ }
+}
+
+
+
+void
+decode_params_bigend(proto_tree *tree, tvbuff_t *tvb, int offset, int len)
+{
+ abort();
+}
diff --git a/fnet/index.html b/fnet/index.html
new file mode 100644
index 00000000000..154c336039a
--- /dev/null
+++ b/fnet/index.html
@@ -0,0 +1,15 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<html>
+ <title>FNET Home Page</title>
+ <body>
+ <h1>FNET Home Page</h1>
+ <ul>
+ <li><a href="README">README</a></li>
+ <li><a href="INSTALL">INSTALL</a></li>
+ <li><a href="RELEASEINFO">RELEASEINFO</a></li>
+ <li><a href="../../doxygen/fnet/">Doxygen Documentation</a></li>
+ <li><a href="protocol.txt">FRT RPC Protocol</a></li>
+ <li><a href="../../doxygen/fnet/spiral_8h.html">SPIRAL API</a></li>
+ </ul>
+ </body>
+</html>
diff --git a/fnet/src/.gitignore b/fnet/src/.gitignore
new file mode 100644
index 00000000000..516a0a876bc
--- /dev/null
+++ b/fnet/src/.gitignore
@@ -0,0 +1,10 @@
+*.dsp
+Makefile.ini
+config_command.sh
+fnet.mak
+project.dsw
+xsync.log
+/gen
+/app_base.info
+/app_test.info
+/app_total.info
diff --git a/fnet/src/Doxyfile b/fnet/src/Doxyfile
new file mode 100644
index 00000000000..bcd82f9a841
--- /dev/null
+++ b/fnet/src/Doxyfile
@@ -0,0 +1,939 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Doxyfile 1.2.15
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# General configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = FNET
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = head
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ../doc/doxygen
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French,
+# German, Greek, Hungarian, Italian, Japanese, Korean, Norwegian, Polish,
+# Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish.
+
+OUTPUT_LANGUAGE = English
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these class will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. It is allowed to use relative paths in the argument list.
+
+STRIP_FROM_PATH =
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower case letters. If set to YES upper case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# users are adviced to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explict @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# reimplements.
+
+INHERIT_DOCS = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consist of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = fnet fnet/frt fnet/testkit
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl
+
+FILE_PATTERNS = *.h *.cpp
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+
+INPUT_FILTER =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse.
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX = FNET_ FRT_
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the Html help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+,
+# or Internet explorer 4.0+). Note that for large projects the tree generation
+# can take a very long time. In such cases it is better to disable this feature.
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimised for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assigments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_XML = NO
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed.
+
+PREDEFINED = IAM_DOXYGEN
+
+# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line and do not end with a semicolon. Such function macros are typically
+# used for boiler-plate code, and will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tagfiles.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
+# option is superceded by the HAVE_DOT option below. This is only a fallback. It is
+# recommended to install and use dot, since it yield more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 1024
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermedate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
+
+# The CGI_NAME tag should be the name of the CGI script that
+# starts the search engine (doxysearch) with the correct parameters.
+# A script with this name will be generated by doxygen.
+
+CGI_NAME = search.cgi
+
+# The CGI_URL tag should be the absolute URL to the directory where the
+# cgi binaries are located. See the documentation of your http daemon for
+# details.
+
+CGI_URL =
+
+# The DOC_URL tag should be the absolute URL to the directory where the
+# documentation is located. If left blank the absolute path to the
+# documentation, with file:// prepended to it, will be used.
+
+DOC_URL =
+
+# The DOC_ABSPATH tag should be the absolute path to the directory where the
+# documentation is located. If left blank the directory on the local machine
+# will be used.
+
+DOC_ABSPATH =
+
+# The BIN_ABSPATH tag must point to the directory where the doxysearch binary
+# is installed.
+
+BIN_ABSPATH = /usr/local/bin/
+
+# The EXT_DOC_PATHS tag can be used to specify one or more paths to
+# documentation generated for other projects. This allows doxysearch to search
+# the documentation for these projects as well.
+
+EXT_DOC_PATHS =
diff --git a/fnet/src/examples/frt/rpc/.gitignore b/fnet/src/examples/frt/rpc/.gitignore
new file mode 100644
index 00000000000..91218a0868b
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/.gitignore
@@ -0,0 +1,16 @@
+.depend
+Makefile
+echo_client
+echo_server
+rpc_callback_client
+rpc_callback_server
+rpc_client
+rpc_info
+rpc_invoke
+rpc_proxy
+rpc_server
+fnet_echo_client_app
+fnet_rpc_callback_client_app
+fnet_rpc_callback_server_app
+fnet_rpc_client_app
+fnet_rpc_server_app
diff --git a/fnet/src/examples/frt/rpc/CMakeLists.txt b/fnet/src/examples/frt/rpc/CMakeLists.txt
new file mode 100644
index 00000000000..aae76bdcd4f
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_rpc_server_app
+ SOURCES
+ rpc_server.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_client_app
+ SOURCES
+ rpc_client.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_echo_client_app
+ SOURCES
+ echo_client.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_info_app
+ SOURCES
+ rpc_info.cpp
+ OUTPUT_NAME rpc_info
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_proxy_app
+ SOURCES
+ rpc_proxy.cpp
+ OUTPUT_NAME rpc_proxy
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_callback_server_app
+ SOURCES
+ rpc_callback_server.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_callback_client_app
+ SOURCES
+ rpc_callback_client.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_rpc_invoke_app
+ SOURCES
+ rpc_invoke.cpp
+ OUTPUT_NAME rpc_invoke
+ INSTALL bin
+ DEPENDS
+ fnet
+)
diff --git a/fnet/src/examples/frt/rpc/echo_client.cpp b/fnet/src/examples/frt/rpc/echo_client.cpp
new file mode 100644
index 00000000000..f34f4c65111
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/echo_client.cpp
@@ -0,0 +1,94 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("echo_client");
+#include <vespa/fnet/frt/frt.h>
+
+class EchoClient : public FastOS_Application
+{
+public:
+ virtual int Main();
+};
+
+int
+EchoClient::Main()
+{
+ if (_argc < 2) {
+ printf("usage : echo_client <connectspec>\n");
+ return 1;
+ }
+ FRT_Supervisor supervisor;
+
+ supervisor.Start();
+ FRT_Target *target = supervisor.GetTarget(_argv[1]);
+ FRT_RPCRequest *req = supervisor.AllocRPCRequest();
+ FRT_Values *args = req->GetParams();
+ req->SetMethodName("frt.rpc.echo");
+ args->EnsureFree(16);
+
+ args->AddInt8(8);
+ uint8_t *pt_int8 = args->AddInt8Array(3);
+ pt_int8[0] = 1;
+ pt_int8[1] = 2;
+ pt_int8[2] = 3;
+
+ args->AddInt16(16);
+ uint16_t *pt_int16 = args->AddInt16Array(3);
+ pt_int16[0] = 2;
+ pt_int16[1] = 4;
+ pt_int16[2] = 6;
+
+ args->AddInt32(32);
+ uint32_t *pt_int32 = args->AddInt32Array(3);
+ pt_int32[0] = 4;
+ pt_int32[1] = 8;
+ pt_int32[2] = 12;
+
+ args->AddInt64(64);
+ uint64_t *pt_int64 = args->AddInt64Array(3);
+ pt_int64[0] = 8;
+ pt_int64[1] = 16;
+ pt_int64[2] = 24;
+
+ args->AddFloat(32.5);
+ float *pt_float = args->AddFloatArray(3);
+ pt_float[0] = 0.25;
+ pt_float[1] = 0.5;
+ pt_float[2] = 0.75;
+
+ args->AddDouble(64.5);
+ double *pt_double = args->AddDoubleArray(3);
+ pt_double[0] = 0.1;
+ pt_double[1] = 0.2;
+ pt_double[2] = 0.3;
+
+ args->AddString("string");
+ FRT_StringValue *pt_string = args->AddStringArray(3);
+ args->SetString(&pt_string[0], "str1");
+ args->SetString(&pt_string[1], "str2");
+ args->SetString(&pt_string[2], "str3");
+
+ args->AddData("data", 4);
+ FRT_DataValue *pt_data = args->AddDataArray(3);
+ args->SetData(&pt_data[0], "dat1", 4);
+ args->SetData(&pt_data[1], "dat2", 4);
+ args->SetData(&pt_data[2], "dat3", 4);
+
+ target->InvokeSync(req, 5.0); // Invoke
+ req->Print(); // Dump request data
+ if (req->GetReturn()->Equals(req->GetParams())) {
+ printf("Return values == parameters.\n");
+ } else {
+ printf("Return values != parameters.\n");
+ }
+ req->SubRef();
+ supervisor.ShutDown(true);
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ EchoClient myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_callback_client.cpp b/fnet/src/examples/frt/rpc/rpc_callback_client.cpp
new file mode 100644
index 00000000000..479eb13fd43
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_callback_client.cpp
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_callback_client");
+#include <vespa/fnet/frt/frt.h>
+
+
+struct RPC : public FRT_Invokable
+{
+ uint32_t invokeCnt;
+ RPC() : invokeCnt(0) {}
+ void Prod(FRT_RPCRequest *req);
+ void Init(FRT_Supervisor *s);
+};
+
+void
+RPC::Prod(FRT_RPCRequest *req)
+{
+ (void) req;
+ ++invokeCnt;
+}
+
+void
+RPC::Init(FRT_Supervisor *s)
+{
+ FRT_ReflectionBuilder rb(s);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("prod", "", "", true,
+ FRT_METHOD(RPC::Prod), this);
+ //-------------------------------------------------------------------
+}
+
+
+class MyApp : public FastOS_Application
+{
+public:
+ virtual int Main();
+};
+
+int
+MyApp::Main()
+{
+ if (_argc < 2) {
+ printf("usage : rpc_server <connectspec>\n");
+ return 1;
+ }
+ RPC rpc;
+ FRT_Supervisor orb;
+ rpc.Init(&orb);
+ orb.Start();
+
+ FRT_Target *target = orb.Get2WayTarget(_argv[1]);
+ FRT_RPCRequest *req = orb.AllocRPCRequest();
+
+ printf("invokeCnt: %d\n", rpc.invokeCnt);
+
+ req->SetMethodName("callBack");
+ req->GetParams()->AddString("prod");
+ target->InvokeSync(req, 10.0);
+
+ if(req->IsError()) {
+ printf("[error(%d): %s]\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ printf("invokeCnt: %d\n", rpc.invokeCnt);
+
+ req = orb.AllocRPCRequest(req);
+ req->SetMethodName("callBack");
+ req->GetParams()->AddString("prod");
+ target->InvokeSync(req, 10.0);
+
+ if(req->IsError()) {
+ printf("[error(%d): %s]\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ printf("invokeCnt: %d\n", rpc.invokeCnt);
+
+ req = orb.AllocRPCRequest(req);
+ req->SetMethodName("callBack");
+ req->GetParams()->AddString("prod");
+ target->InvokeSync(req, 10.0);
+
+ if(req->IsError()) {
+ printf("[error(%d): %s]\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ printf("invokeCnt: %d\n", rpc.invokeCnt);
+
+ req->SubRef();
+ target->SubRef();
+ orb.ShutDown(true);
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ MyApp myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_callback_server.cpp b/fnet/src/examples/frt/rpc/rpc_callback_server.cpp
new file mode 100644
index 00000000000..05d3a205a29
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_callback_server.cpp
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_callback_server");
+#include <vespa/fnet/frt/frt.h>
+
+
+struct RPC : public FRT_Invokable
+{
+ void CallBack(FRT_RPCRequest *req);
+ void Init(FRT_Supervisor *s);
+};
+
+void
+RPC::CallBack(FRT_RPCRequest *req)
+{
+ FNET_Connection *conn = req->GetConnection();
+ FRT_RPCRequest *cb = new FRT_RPCRequest();
+ cb->SetMethodName(req->GetParams()->GetValue(0)._string._str);
+ FRT_Supervisor::InvokeSync(conn->Owner(), conn, cb, 5.0);
+ if(cb->IsError()) {
+ printf("[error(%d): %s]\n",
+ cb->GetErrorCode(),
+ cb->GetErrorMessage());
+ }
+ cb->SubRef();
+}
+
+void
+RPC::Init(FRT_Supervisor *s)
+{
+ FRT_ReflectionBuilder rb(s);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("callBack", "s", "", false,
+ FRT_METHOD(RPC::CallBack), this);
+ //-------------------------------------------------------------------
+}
+
+
+class MyApp : public FastOS_Application
+{
+public:
+ virtual int Main();
+};
+
+int
+MyApp::Main()
+{
+ FNET_SignalShutDown::hookSignals();
+ if (_argc < 2) {
+ printf("usage : rpc_server <listenspec>\n");
+ return 1;
+ }
+ RPC rpc;
+ FRT_Supervisor orb;
+ rpc.Init(&orb);
+ orb.Listen(_argv[1]);
+ FNET_SignalShutDown ssd(*orb.GetTransport());
+ orb.Main();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ MyApp myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_client.cpp b/fnet/src/examples/frt/rpc/rpc_client.cpp
new file mode 100644
index 00000000000..cde39270698
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_client.cpp
@@ -0,0 +1,92 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_client");
+#include <vespa/fnet/frt/frt.h>
+
+
+class RPCClient : public FastOS_Application
+{
+public:
+ virtual int Main();
+};
+
+int
+RPCClient::Main()
+{
+ if (_argc < 2) {
+ printf("usage : rpc_client <connectspec>\n");
+ return 1;
+ }
+ FRT_Supervisor supervisor;
+
+ supervisor.Start();
+ FRT_Target *target = supervisor.GetTarget(_argv[1]);
+
+ const char *str1 = "abc";
+ const char *str2 = "def";
+ float float1 = 20.5;
+ float float2 = 60.5;
+ double double1 = 25.5;
+ double double2 = 5.5;
+
+ fprintf(stdout, "\nTesting concat method\n");
+ FRT_RPCRequest *req = supervisor.AllocRPCRequest();
+ req->SetMethodName("concat");
+ req->GetParams()->AddString(str1);
+ req->GetParams()->AddString(str2);
+ target->InvokeSync(req, 5.0);
+ if (req->GetErrorCode() == FRTE_NO_ERROR) {
+ fprintf(stdout, "%s + %s = %s\n", str1, str2,
+ req->GetReturn()->GetValue(0)._string._str);
+ } else {
+ fprintf(stdout, "error(%d): %s\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ fprintf(stdout, "\nTesting addFloat method\n");
+ req->SubRef();
+ req = supervisor.AllocRPCRequest();
+ req->SetMethodName("addFloat");
+ req->GetParams()->AddFloat(float1);
+ req->GetParams()->AddFloat(float2);
+ target->InvokeSync(req, 5.0);
+ if (req->GetErrorCode() == FRTE_NO_ERROR) {
+ fprintf(stdout, "%f + %f = %f\n", float1, float2,
+ req->GetReturn()->GetValue(0)._float);
+ } else {
+ fprintf(stdout, "error(%d): %s\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ fprintf(stdout, "\nTesting addDouble method\n");
+ req->SubRef();
+ req = supervisor.AllocRPCRequest();
+ req->SetMethodName("addDouble");
+ req->GetParams()->AddDouble(double1);
+ req->GetParams()->AddDouble(double2);
+ target->InvokeSync(req, 5.0);
+ if (req->GetErrorCode() == FRTE_NO_ERROR) {
+ fprintf(stdout, "%f + %f = %f\n", double1, double2,
+ req->GetReturn()->GetValue(0)._double);
+ } else {
+ fprintf(stdout, "error(%d): %s\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ }
+
+ req->SubRef();
+ target->SubRef();
+ supervisor.ShutDown(true);
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ RPCClient myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_info.cpp b/fnet/src/examples/frt/rpc/rpc_info.cpp
new file mode 100644
index 00000000000..af15a19b6a4
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_info.cpp
@@ -0,0 +1,140 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_info");
+#include <vespa/fnet/frt/frt.h>
+
+class RPCInfo : public FastOS_Application
+{
+public:
+
+ void GetReq(FRT_RPCRequest **req, FRT_Supervisor *supervisor)
+ {
+ if ((*req) != NULL)
+ (*req)->SubRef();
+ (*req) = supervisor->AllocRPCRequest();
+ }
+
+ void FreeReqs(FRT_RPCRequest *r1, FRT_RPCRequest *r2)
+ {
+ if (r1 != NULL)
+ r1->SubRef();
+ if (r2 != NULL)
+ r2->SubRef();
+ }
+
+ void DumpMethodInfo(const char *indent, FRT_RPCRequest *info,
+ const char *name)
+ {
+ if (info->IsError()) {
+ printf("%sMETHOD %s\n", indent, name);
+ printf("%s [error(%d): %s]\n\n", indent,
+ info->GetErrorCode(),
+ info->GetErrorMessage());
+ return;
+ }
+
+ const char *desc = info->GetReturn()->GetValue(0)._string._str;
+ const char *arg = info->GetReturn()->GetValue(1)._string._str;
+ const char *ret = info->GetReturn()->GetValue(2)._string._str;
+ uint32_t argCnt = strlen(arg);
+ uint32_t retCnt = strlen(ret);
+ FRT_StringValue *argName = info->GetReturn()->GetValue(3)._string_array._pt;
+ FRT_StringValue *argDesc = info->GetReturn()->GetValue(4)._string_array._pt;
+ FRT_StringValue *retName = info->GetReturn()->GetValue(5)._string_array._pt;
+ FRT_StringValue *retDesc = info->GetReturn()->GetValue(6)._string_array._pt;
+
+ printf("%sMETHOD %s\n", indent, name);
+ printf("%s DESCRIPTION:\n"
+ "%s %s\n", indent, indent, desc);
+
+ if (argCnt > 0) {
+ printf("%s PARAMS:\n", indent);
+ for (uint32_t a = 0; a < argCnt; a++)
+ printf("%s [%c][%s] %s\n", indent, arg[a], argName[a]._str,
+ argDesc[a]._str);
+ }
+
+ if (retCnt > 0) {
+ printf("%s RETURN:\n", indent);
+ for (uint32_t r = 0; r < retCnt; r++)
+ printf("%s [%c][%s] %s\n", indent, ret[r], retName[r]._str,
+ retDesc[r]._str);
+ }
+ printf("\n");
+ }
+
+ virtual int Main();
+};
+
+
+int
+RPCInfo::Main()
+{
+ if (_argc < 2) {
+ printf("usage : rpc_info <connectspec> [verbose]\n");
+ return 1;
+ }
+
+ bool verbose = (_argc > 2 && strcmp(_argv[2], "verbose") == 0);
+ FRT_Supervisor supervisor;
+ FRT_Target *target = supervisor.GetTarget(_argv[1]);
+ FRT_RPCRequest *m_list = NULL;
+ FRT_RPCRequest *info = NULL;
+ supervisor.Start();
+
+ GetReq(&info, &supervisor);
+ info->SetMethodName("frt.rpc.ping");
+ target->InvokeSync(info, 5.0);
+ if (info->IsError()) {
+ fprintf(stderr, "Error talking to %s\n", _argv[1]);
+ FreeReqs(m_list, info);
+ supervisor.ShutDown(true);
+ return 1;
+ }
+
+ GetReq(&m_list, &supervisor);
+ m_list->SetMethodName("frt.rpc.getMethodList");
+ target->InvokeSync(m_list, 5.0);
+
+ if (!m_list->IsError()) {
+
+ uint32_t numMethods = m_list->GetReturn()->GetValue(0)._string_array._len;
+ FRT_StringValue *methods = m_list->GetReturn()->GetValue(0)._string_array._pt;
+ FRT_StringValue *arglist = m_list->GetReturn()->GetValue(1)._string_array._pt;
+ FRT_StringValue *retlist = m_list->GetReturn()->GetValue(2)._string_array._pt;
+
+ for (uint32_t m = 0; m < numMethods; m++) {
+
+ if (verbose) {
+
+ GetReq(&info, &supervisor);
+ info->SetMethodName("frt.rpc.getMethodInfo");
+ info->GetParams()->AddString(methods[m]._str);
+ target->InvokeSync(info, 5.0);
+ DumpMethodInfo("", info, methods[m]._str);
+
+ } else {
+
+ printf("METHOD [%s] <- %s <- [%s]\n",
+ retlist[m]._str, methods[m]._str, arglist[m]._str);
+ }
+ }
+ } else {
+ fprintf(stderr, " [error(%d): %s]\n",
+ m_list->GetErrorCode(),
+ m_list->GetErrorMessage());
+ }
+ FreeReqs(m_list, info);
+ target->SubRef();
+ supervisor.ShutDown(true);
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ RPCInfo myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_invoke.cpp b/fnet/src/examples/frt/rpc/rpc_invoke.cpp
new file mode 100644
index 00000000000..471fd9a879f
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_invoke.cpp
@@ -0,0 +1,107 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_invoke");
+#include <vespa/fnet/frt/frt.h>
+
+class RPCClient : public FastOS_Application
+{
+private:
+ static bool addArg(FRT_RPCRequest *req, const char *param) {
+ int len = strlen(param);
+ if (len < 2 || param[1] != ':') {
+ return false;
+ }
+ const char *value = param + 2;
+ switch (param[0]) {
+ case 'b':
+ req->GetParams()->AddInt8(strtoll(value, NULL, 0));
+ break;
+ case 'h':
+ req->GetParams()->AddInt16(strtoll(value, NULL, 0));
+ break;
+ case 'i':
+ req->GetParams()->AddInt32(strtoll(value, NULL, 0));
+ break;
+ case 'l':
+ req->GetParams()->AddInt64(strtoll(value, NULL, 0));
+ break;
+ case 'f':
+ req->GetParams()->AddFloat(strtod(value, NULL));
+ break;
+ case 'd':
+ req->GetParams()->AddDouble(strtod(value, NULL));
+ break;
+ case 's':
+ req->GetParams()->AddString(value);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+public:
+ virtual int Main();
+};
+
+int
+RPCClient::Main()
+{
+ if (_argc < 3) {
+ fprintf(stderr, "usage: rpc_invoke [-t timeout] <connectspec> <method> [args]\n");
+ fprintf(stderr, " -t timeout in seconds\n");
+ fprintf(stderr, " Each arg must be on the form <type>:<value>\n");
+ fprintf(stderr, " supported types: {'b','h','i','l','f','d','s'}\n");
+ return 1;
+ }
+ int retCode = 0;
+ FRT_Supervisor supervisor;
+ supervisor.Start();
+ int targetArg = 1;
+ int methNameArg = 2;
+ int startOfArgs = 3;
+ int timeOut = 10;
+ if (strcmp(_argv[1], "-t") == 0) {
+ timeOut = atoi(_argv[2]);
+ targetArg = 3;
+ methNameArg = 4;
+ startOfArgs = 5;
+ }
+ FRT_Target *target = supervisor.GetTarget(_argv[targetArg]);
+ FRT_RPCRequest *req = supervisor.AllocRPCRequest();
+ req->SetMethodName(_argv[methNameArg]);
+ for (int i = startOfArgs; i < _argc; ++i) {
+ if (!addArg(req, _argv[i])) {
+ fprintf(stderr, "could not parse parameter: '%s'\n", _argv[i]);
+ retCode = 2;
+ break;
+ }
+ }
+ if (retCode == 0) {
+ fprintf(stdout, "PARAMETERS:\n");
+ req->GetParams()->Print();
+ target->InvokeSync(req, (double)timeOut);
+ if (req->GetErrorCode() == FRTE_NO_ERROR) {
+ fprintf(stdout, "RETURN VALUES:\n");
+ req->GetReturn()->Print();
+ } else {
+ fprintf(stderr, "error(%d): %s\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ retCode = 3;
+ }
+ }
+ req->SubRef();
+ target->SubRef();
+ supervisor.ShutDown(true);
+ return retCode;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ RPCClient myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_proxy.cpp b/fnet/src/examples/frt/rpc/rpc_proxy.cpp
new file mode 100644
index 00000000000..dd29255093a
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_proxy.cpp
@@ -0,0 +1,254 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_proxy");
+#include <vespa/fnet/frt/frt.h>
+
+//-----------------------------------------------------------------------------
+
+struct Session
+{
+ FNET_Connection *client;
+ FRT_Target *server;
+ uint32_t id;
+ uint32_t finiCnt;
+
+ Session(uint32_t xid) : client(NULL), server(NULL), id(xid), finiCnt(0) {}
+ ~Session() { assert(client == NULL && server == NULL && finiCnt == 2); }
+
+private:
+ Session(const Session &);
+ Session &operator=(const Session &);
+};
+
+//-----------------------------------------------------------------------------
+
+class RPCProxy : public FRT_Invokable
+{
+private:
+ FRT_Supervisor &_supervisor;
+ const char *_spec;
+ bool _verbose;
+ uint32_t _currID;
+ char _prefixStr[256];
+
+ RPCProxy(const RPCProxy &);
+ RPCProxy &operator=(const RPCProxy &);
+
+public:
+ RPCProxy(FRT_Supervisor &supervisor,
+ const char *spec,
+ bool verbose)
+ : _supervisor(supervisor),
+ _spec(spec),
+ _verbose(verbose),
+ _currID(0),
+ _prefixStr() {}
+
+ bool IsVerbose() const { return _verbose; }
+ const char *GetPrefix(FRT_RPCRequest *req);
+ void PrintMethod(FRT_RPCRequest *req, const char *desc);
+ void Done(FRT_RPCRequest *req);
+ void HOOK_Mismatch(FRT_RPCRequest *req);
+ void HOOK_Init(FRT_RPCRequest *req);
+ void HOOK_Down(FRT_RPCRequest *req);
+ void HOOK_Fini(FRT_RPCRequest *req);
+ static Session *GetSession(FRT_RPCRequest *req)
+ {
+ return (Session *) req->GetConnection()->GetContext()._value.VOIDP;
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+class ReqDone : public FRT_IRequestWait
+{
+private:
+ RPCProxy &_proxy;
+
+public:
+ ReqDone(RPCProxy &proxy) : _proxy(proxy) {}
+ virtual void RequestDone(FRT_RPCRequest *req);
+};
+
+void
+ReqDone::RequestDone(FRT_RPCRequest *req)
+{
+ _proxy.Done(req);
+}
+
+//-----------------------------------------------------------------------------
+
+const char *
+RPCProxy::GetPrefix(FRT_RPCRequest *req)
+{
+ FastOS_Time t;
+ tm currTime;
+ tm *currTimePt;
+
+ t.SetNow();
+ time_t secs = t.Secs();
+ currTimePt = localtime_r(&secs, &currTime);
+ assert(currTimePt == &currTime);
+ (void) currTimePt;
+
+ char rid[32];
+ if (req->GetContext()._value.CHANNEL != NULL) {
+ sprintf(rid, "[rid=%u]", req->GetContext()._value.CHANNEL->GetID());
+ } else {
+ rid[0] = '\0';
+ }
+
+ sprintf(_prefixStr, "[%04d-%02d-%02d %02d:%02d:%02d:%03d][sid=%u]%s",
+ currTime.tm_year + 1900,
+ currTime.tm_mon + 1,
+ currTime.tm_mday,
+ currTime.tm_hour,
+ currTime.tm_min,
+ currTime.tm_sec,
+ (int)(t.GetMicroSeconds() / 1000),
+ GetSession(req)->id,
+ rid);
+
+ return _prefixStr;
+}
+
+
+void
+RPCProxy::PrintMethod(FRT_RPCRequest *req, const char *desc)
+{
+ fprintf(stdout, "%s %s: %s\n", GetPrefix(req), desc,
+ req->GetMethodName());
+}
+
+
+void
+RPCProxy::Done(FRT_RPCRequest *req)
+{
+ PrintMethod(req, "RETURN");
+ if (IsVerbose()) {
+ req->GetReturn()->Print(8);
+ }
+ req->Return();
+}
+
+
+void
+RPCProxy::HOOK_Mismatch(FRT_RPCRequest *req)
+{
+ PrintMethod(req, "INVOKE");
+ if (IsVerbose()) {
+ req->GetParams()->Print(8);
+ }
+ req->Detach();
+ req->SetError(FRTE_NO_ERROR, "");
+ if (req->GetConnection()->IsServer() &&
+ GetSession(req)->server != NULL)
+ {
+ GetSession(req)->server->InvokeAsync(req, 60.0,
+ new (req->GetMemoryTub())
+ ReqDone(*this));
+ } else if (req->GetConnection()->IsClient() &&
+ GetSession(req)->client != NULL)
+ {
+ FRT_Supervisor::InvokeAsync(GetSession(req)->client->Owner(),
+ GetSession(req)->client,
+ req, 60.0,
+ new (req->GetMemoryTub())
+ ReqDone(*this));
+ } else {
+ req->SetError(FRTE_RPC_CONNECTION);
+ req->Return();
+ }
+}
+
+
+void
+RPCProxy::HOOK_Init(FRT_RPCRequest *req)
+{
+ if (req->GetConnection()->IsClient()) {
+ return;
+ }
+ Session *session = new Session(_currID++);
+ session->client = req->GetConnection();
+ session->server =
+ _supervisor.Get2WayTarget(_spec,
+ FNET_Context((void *) session));
+ session->client->SetContext(FNET_Context((void *) session));
+ if (session->server->GetConnection() == NULL ||
+ session->server->GetConnection()->GetState()
+ > FNET_Connection::FNET_CONNECTED)
+ {
+ session->finiCnt = 1;
+ session->client->Owner()->Close(session->client);
+ }
+ fprintf(stdout, "%s INIT\n", GetPrefix(req));
+}
+
+
+void
+RPCProxy::HOOK_Down(FRT_RPCRequest *req)
+{
+ Session *session = GetSession(req);
+ if (req->GetConnection()->IsClient()) {
+ if (session->client != NULL) {
+ session->client->Owner()->Close(session->client);
+ }
+ } else {
+ session->server->SubRef();
+ session->client = NULL;
+ session->server = NULL;
+ }
+}
+
+
+void
+RPCProxy::HOOK_Fini(FRT_RPCRequest *req)
+{
+ if (++GetSession(req)->finiCnt == 2) {
+ fprintf(stdout, "%s FINI\n", GetPrefix(req));
+ delete GetSession(req);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+class App : public FastOS_Application
+{
+public:
+ virtual int Main();
+};
+
+int
+App::Main()
+{
+ FNET_SignalShutDown::hookSignals();
+ // would like to turn off FNET logging somehow
+ if (_argc < 3) {
+ fprintf(stderr, "usage: %s <listenspec> <connectspec> [verbose]\n", _argv[0]);
+ return 1;
+ }
+ bool verbose = (_argc > 3) && (strcmp(_argv[3], "verbose") == 0);
+
+ FRT_Supervisor supervisor;
+ RPCProxy proxy(supervisor, _argv[2], verbose);
+
+ supervisor.GetReflectionManager()->Reset();
+ supervisor.SetSessionInitHook(FRT_METHOD(RPCProxy::HOOK_Init), &proxy);
+ supervisor.SetSessionDownHook(FRT_METHOD(RPCProxy::HOOK_Down), &proxy);
+ supervisor.SetSessionFiniHook(FRT_METHOD(RPCProxy::HOOK_Fini), &proxy);
+ supervisor.SetMethodMismatchHook(FRT_METHOD(RPCProxy::HOOK_Mismatch),
+ &proxy);
+ supervisor.Listen(_argv[1]);
+ FNET_SignalShutDown ssd(*supervisor.GetTransport());
+ supervisor.Main();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ App app;
+ return app.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/frt/rpc/rpc_server.cpp b/fnet/src/examples/frt/rpc/rpc_server.cpp
new file mode 100644
index 00000000000..5e8dd744fdc
--- /dev/null
+++ b/fnet/src/examples/frt/rpc/rpc_server.cpp
@@ -0,0 +1,125 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("rpc_server");
+#include <vespa/fnet/frt/frt.h>
+
+
+class RPCServer : public FRT_Invokable
+{
+private:
+ FRT_Supervisor *_supervisor;
+
+ RPCServer(const RPCServer &);
+ RPCServer &operator=(const RPCServer &);
+
+public:
+ RPCServer() : _supervisor(NULL) {}
+ void InitRPC(FRT_Supervisor *s);
+ void RPC_concat(FRT_RPCRequest *req);
+ void RPC_addFloat(FRT_RPCRequest *req);
+ void RPC_addDouble(FRT_RPCRequest *req);
+ int Main(int argc, char **argv);
+};
+
+void
+RPCServer::InitRPC(FRT_Supervisor *s)
+{
+ FRT_ReflectionBuilder rb(s);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("concat", "ss", "s", true,
+ FRT_METHOD(RPCServer::RPC_concat), this);
+ rb.MethodDesc("Concatenate two strings");
+ rb.ParamDesc("string1", "a string");
+ rb.ParamDesc("string2", "another string");
+ rb.ReturnDesc("ret", "the concatenation of string1 and string2");
+ //-------------------------------------------------------------------
+ rb.DefineMethod("addFloat", "ff", "f", true,
+ FRT_METHOD(RPCServer::RPC_addFloat), this);
+ rb.MethodDesc("Add two floats");
+ rb.ParamDesc("float1", "a float");
+ rb.ParamDesc("float2", "another float");
+ rb.ReturnDesc("ret", "float1 + float2");
+ //-------------------------------------------------------------------
+ rb.DefineMethod("addDouble", "dd", "d", true,
+ FRT_METHOD(RPCServer::RPC_addDouble), this);
+ rb.MethodDesc("Add two doubles");
+ rb.ParamDesc("double1", "a double");
+ rb.ParamDesc("double2", "another double");
+ rb.ReturnDesc("ret", "double1 + double2");
+ //-------------------------------------------------------------------
+}
+
+void
+RPCServer::RPC_concat(FRT_RPCRequest *req)
+{
+ FRT_Values &params = *req->GetParams();
+ FRT_Values &ret = *req->GetReturn();
+
+ uint32_t len = (params[0]._string._len +
+ params[1]._string._len);
+ char *tmp = ret.AddString(len);
+ strcpy(tmp, params[0]._string._str);
+ strcat(tmp, params[1]._string._str);
+}
+
+void
+RPCServer::RPC_addFloat(FRT_RPCRequest *req)
+{
+ FRT_Values &params = *req->GetParams();
+ FRT_Values &ret = *req->GetReturn();
+
+ ret.AddFloat(params[0]._float + params[1]._float);
+}
+
+void
+RPCServer::RPC_addDouble(FRT_RPCRequest *req)
+{
+ FRT_Values &params = *req->GetParams();
+ FRT_Values &ret = *req->GetReturn();
+
+ ret.AddDouble(params[0]._double + params[1]._double);
+}
+
+int
+RPCServer::Main(int argc, char **argv)
+{
+ FNET_SignalShutDown::hookSignals();
+ if (argc < 2) {
+ printf("usage : rpc_server <listenspec>\n");
+ return 1;
+ }
+
+ _supervisor = new FRT_Supervisor();
+ InitRPC(_supervisor);
+ _supervisor->Listen(argv[1]);
+ FNET_SignalShutDown ssd(*_supervisor->GetTransport());
+ _supervisor->Main();
+ delete _supervisor;
+ return 0;
+}
+
+
+class App : public FastOS_Application
+{
+private:
+ RPCServer _server;
+
+public:
+ App() : _server() {}
+ virtual int Main();
+};
+
+int
+App::Main()
+{
+ return _server.Main(_argc, _argv);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ App myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/ping/.gitignore b/fnet/src/examples/ping/.gitignore
new file mode 100644
index 00000000000..96bd86822ad
--- /dev/null
+++ b/fnet/src/examples/ping/.gitignore
@@ -0,0 +1,9 @@
+*.core
+*.ilk
+*.pdb
+.depend
+Makefile
+pingclient
+pingserver
+fnet_pingclient_app
+fnet_pingserver_app
diff --git a/fnet/src/examples/ping/CMakeLists.txt b/fnet/src/examples/ping/CMakeLists.txt
new file mode 100644
index 00000000000..7354ec9cf48
--- /dev/null
+++ b/fnet/src/examples/ping/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_pingserver_app
+ SOURCES
+ packets.cpp
+ pingserver.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
+vespa_add_executable(fnet_pingclient_app
+ SOURCES
+ packets.cpp
+ pingclient.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
diff --git a/fnet/src/examples/ping/packets.cpp b/fnet/src/examples/ping/packets.cpp
new file mode 100644
index 00000000000..8958666b374
--- /dev/null
+++ b/fnet/src/examples/ping/packets.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+#include "packets.h"
+
+uint32_t
+PingRequest::GetPCODE()
+{
+ return PCODE_PING_REQUEST;
+}
+
+uint32_t
+PingRequest::GetLength()
+{
+ return 0;
+}
+
+void
+PingRequest::Encode(FNET_DataBuffer *)
+{
+}
+
+bool
+PingRequest::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ src->DataToDead(len);
+ return (len == 0);
+}
+
+
+uint32_t
+PingReply::GetPCODE()
+{
+ return PCODE_PING_REPLY;
+}
+
+uint32_t
+PingReply::GetLength()
+{
+ return 0;
+}
+
+void
+PingReply::Encode(FNET_DataBuffer *)
+{
+}
+
+bool
+PingReply::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ src->DataToDead(len);
+ return (len == 0);
+}
+
+
+FNET_Packet *
+PingPacketFactory::CreatePacket(uint32_t pcode, FNET_Context)
+{
+ switch(pcode) {
+ case PCODE_PING_REQUEST: return new PingRequest();
+ case PCODE_PING_REPLY: return new PingReply();
+ }
+ return NULL;
+}
diff --git a/fnet/src/examples/ping/packets.h b/fnet/src/examples/ping/packets.h
new file mode 100644
index 00000000000..39dae75a16f
--- /dev/null
+++ b/fnet/src/examples/ping/packets.h
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+
+enum {
+ PCODE_PING_REQUEST = 1,
+ PCODE_PING_REPLY = 2
+};
+
+
+class PingRequest : public FNET_Packet
+{
+public:
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+};
+
+
+class PingReply : public FNET_Packet
+{
+public:
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+};
+
+
+class PingPacketFactory : public FNET_IPacketFactory
+{
+public:
+ virtual FNET_Packet *CreatePacket(uint32_t pcode, FNET_Context);
+};
+
diff --git a/fnet/src/examples/ping/pingclient.cpp b/fnet/src/examples/ping/pingclient.cpp
new file mode 100644
index 00000000000..075c0b5df32
--- /dev/null
+++ b/fnet/src/examples/ping/pingclient.cpp
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("pingclient");
+#include <vespa/fnet/fnet.h>
+#include <examples/ping/packets.h>
+
+
+class PingClient : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+
+int
+PingClient::Main()
+{
+ if (_argc < 2) {
+ printf("usage : pingclient <connectspec>\n");
+ printf("example: pingclient 'tcp/localhost:8000'\n");
+ return 1;
+ }
+
+ FNET_PacketQueue queue;
+ FastOS_ThreadPool pool(65000);
+ PingPacketFactory factory;
+ FNET_SimplePacketStreamer streamer(&factory);
+ FNET_Transport transport;
+ FNET_Connection *conn = transport.Connect(_argv[1], &streamer);
+ FNET_Channel *channels[10];
+ transport.Start(&pool);
+
+ uint32_t channelCnt = 0;
+ for (uint32_t i = 0; i < 10; i++) {
+ channels[i] = (conn == NULL) ? NULL : conn->OpenChannel(&queue, FNET_Context(i));
+ if (channels[i] == 0) {
+ fprintf(stderr, "Could not make channel[%d] to %s\n", i, _argv[1]);
+ break;
+ }
+ channelCnt++;
+ channels[i]->Send(new PingRequest());
+ channels[i]->Sync();
+ fprintf(stderr, "Sent ping in context %d\n", i);
+ }
+
+ FNET_Packet *packet;
+ FNET_Context context;
+ while (channelCnt > 0) {
+ packet = queue.DequeuePacket(5000, &context);
+ if (packet == NULL) {
+ fprintf(stderr, "Timeout\n");
+ for(int c = 0; c < 10; c++) {
+ if (channels[c] != NULL) {
+ channels[c]->Close();
+ channels[c]->Free();
+ channels[c] = NULL;
+ fprintf(stderr, "Closed channel with context %d\n", c);
+ }
+ }
+ break;
+ }
+ if (packet->GetPCODE() == PCODE_PING_REPLY) {
+ fprintf(stderr, "Got ping result in context %d\n",
+ context._value.INT);
+ } else if (packet->IsChannelLostCMD()) {
+ fprintf(stderr, "Lost channel with context %d\n",
+ context._value.INT);
+ }
+ if (channels[context._value.INT] != NULL) {
+ channels[context._value.INT]->Close();
+ channels[context._value.INT]->Free();
+ channels[context._value.INT] = NULL;
+ fprintf(stderr, "Closed channel with context %d\n",
+ context._value.INT);
+ channelCnt--;
+ }
+ packet->Free();
+ }
+ if (conn != NULL)
+ conn->SubRef();
+ transport.ShutDown(true);
+ pool.Close();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ PingClient myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/ping/pingserver.cpp b/fnet/src/examples/ping/pingserver.cpp
new file mode 100644
index 00000000000..346307a017c
--- /dev/null
+++ b/fnet/src/examples/ping/pingserver.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("pingserver");
+#include <vespa/fnet/fnet.h>
+#include <examples/ping/packets.h>
+
+
+class PingServer : public FNET_IServerAdapter,
+ public FNET_IPacketHandler,
+ public FastOS_Application
+{
+public:
+ bool InitAdminChannel(FNET_Channel *) { return false; }
+ bool InitChannel(FNET_Channel *channel, uint32_t)
+ {
+ channel->SetContext(FNET_Context(channel));
+ channel->SetHandler(this);
+ return true;
+ }
+
+ HP_RetCode HandlePacket(FNET_Packet *packet, FNET_Context context)
+ {
+ if (packet->GetPCODE() == PCODE_PING_REQUEST) {
+ fprintf(stderr, "Got ping request, sending ping reply\n");
+ context._value.CHANNEL->Send(new PingReply());
+ }
+ packet->Free();
+ return FNET_FREE_CHANNEL;
+ }
+
+ int Main();
+};
+
+
+int
+PingServer::Main()
+{
+ FNET_SignalShutDown::hookSignals();
+ if (_argc < 2) {
+ printf("usage : pingserver <listenspec>\n");
+ printf("example: pingserver 'tcp/8000'\n");
+ return 1;
+ }
+
+ FNET_Transport transport;
+ PingPacketFactory factory;
+ FNET_SimplePacketStreamer streamer(&factory);
+ FNET_Connector *listener =
+ transport.Listen(_argv[1], &streamer, this);
+ if (listener != NULL)
+ listener->SubRef();
+
+ FNET_SignalShutDown ssd(transport);
+ transport.Main();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ PingServer myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/proxy/.gitignore b/fnet/src/examples/proxy/.gitignore
new file mode 100644
index 00000000000..a2ec3289ec2
--- /dev/null
+++ b/fnet/src/examples/proxy/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+proxy
+fnet_proxy_app
diff --git a/fnet/src/examples/proxy/CMakeLists.txt b/fnet/src/examples/proxy/CMakeLists.txt
new file mode 100644
index 00000000000..bc5dce755a7
--- /dev/null
+++ b/fnet/src/examples/proxy/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_proxy_app
+ SOURCES
+ proxy.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
diff --git a/fnet/src/examples/proxy/proxy.cpp b/fnet/src/examples/proxy/proxy.cpp
new file mode 100644
index 00000000000..9a76077591f
--- /dev/null
+++ b/fnet/src/examples/proxy/proxy.cpp
@@ -0,0 +1,244 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("proxy");
+#include <vespa/fnet/fnet.h>
+
+
+class RawPacket : public FNET_Packet
+{
+private:
+ FNET_DataBuffer _data;
+
+public:
+ RawPacket() : _data() {}
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+};
+
+uint32_t
+RawPacket::GetPCODE()
+{
+ return 0;
+}
+
+uint32_t
+RawPacket::GetLength()
+{
+ return _data.GetDataLen();
+}
+
+void
+RawPacket::Encode(FNET_DataBuffer *dst)
+{
+ dst->WriteBytes(_data.GetData(), _data.GetDataLen());
+}
+
+bool
+RawPacket::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ _data.WriteBytes(src->GetData(), len);
+ src->DataToDead(len);
+ return true;
+}
+
+
+class Bridge : public FNET_IPacketHandler
+{
+private:
+ FNET_Channel *_client;
+ FNET_Connection *_server;
+
+ Bridge(const Bridge &);
+ Bridge &operator=(const Bridge &);
+
+public:
+ Bridge() : _client(NULL), _server(NULL) {}
+
+ enum packet_source {
+ CLIENT = 0,
+ SERVER = 1
+ };
+
+ void SetConns(FNET_Channel *client,
+ FNET_Connection *server)
+ {
+ _client = client;
+ _server = server;
+ }
+
+ virtual HP_RetCode HandlePacket(FNET_Packet *packet,
+ FNET_Context context);
+};
+
+
+FNET_IPacketHandler::HP_RetCode
+Bridge::HandlePacket(FNET_Packet *packet, FNET_Context context)
+{
+ HP_RetCode ret = FNET_KEEP_CHANNEL;
+
+ if (packet->IsChannelLostCMD()) {
+
+ if (context._value.INT == CLIENT) {
+
+ if (_server != NULL) {
+ LOG(info, "client connection lost");
+ _server->Owner()->Close(_server);
+ }
+ ret = FNET_FREE_CHANNEL;
+ _client = NULL;
+
+ } else if (context._value.INT == SERVER) {
+
+ if (_client != NULL) {
+ LOG(info, "server connection lost");
+ _client->GetConnection()->Owner()->Close(_client->GetConnection());
+ }
+ _server->SubRef();
+ _server = NULL;
+
+ }
+
+ if (_client == NULL && _server == NULL)
+ delete this;
+
+ } else {
+
+ if (context._value.INT == CLIENT) {
+ if (_server != NULL)
+ _server->PostPacket(packet, FNET_NOID);
+ else
+ packet->Free();
+
+ } else if (context._value.INT == SERVER) {
+ if (_client != NULL)
+ _client->Send(packet);
+ else
+ packet->Free();
+
+ }
+ }
+
+ // The admin channel on a client connection (in this case, the
+ // connection with the server) are freed when the connection
+ // object is destructed. The admin channel on a server connection
+ // however (in this case the channel connecting us with the
+ // client) must be treated as a normal channel.
+
+ return ret;
+}
+
+
+class Proxy : public FNET_IServerAdapter,
+ public FNET_IPacketStreamer,
+ public FastOS_Application
+{
+private:
+ FNET_Transport _transport;
+
+public:
+ Proxy() : _transport() {}
+ virtual bool GetPacketInfo(FNET_DataBuffer *src, uint32_t *plen,
+ uint32_t *pcode, uint32_t *chid, bool *);
+ virtual FNET_Packet *Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t pcode, FNET_Context);
+ virtual void Encode(FNET_Packet *packet, uint32_t chid,
+ FNET_DataBuffer *dst);
+ // ---------------------------------------------
+ virtual bool InitAdminChannel(FNET_Channel *channel);
+ virtual bool InitChannel(FNET_Channel *, uint32_t);
+ // ---------------------------------------------
+ virtual int Main();
+};
+
+
+bool
+Proxy::GetPacketInfo(FNET_DataBuffer *src, uint32_t *plen,
+ uint32_t *pcode, uint32_t *chid, bool *)
+{
+ if (src->GetDataLen() == 0) {
+ return false;
+ }
+ *pcode = 0;
+ *plen = src->GetDataLen();
+ *chid = FNET_NOID;
+ return true;
+}
+
+FNET_Packet *
+Proxy::Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t, FNET_Context)
+{
+ RawPacket *packet = new RawPacket();
+ packet->Decode(src, plen);
+ return packet;
+}
+
+void
+Proxy::Encode(FNET_Packet *packet, uint32_t chid,
+ FNET_DataBuffer *dst)
+{
+ uint32_t pcode = packet->GetPCODE();
+ uint32_t len = packet->GetLength();
+ (void) pcode;
+ (void) chid;
+ (void) len;
+ packet->Encode(dst);
+}
+
+// ---------------------------------------------
+
+bool
+Proxy::InitAdminChannel(FNET_Channel *channel)
+{
+ Bridge *bridge = new Bridge();
+ FNET_Connection *server = _transport.Connect(_argv[2], this, bridge,
+ FNET_Context(Bridge::SERVER));
+ if (server == NULL) {
+ channel->GetConnection()->Owner()->Close(channel->GetConnection());
+ delete bridge;
+ return false;
+ }
+ bridge->SetConns(channel, server);
+ channel->SetHandler(bridge);
+ channel->SetContext(FNET_Context((uint32_t)Bridge::CLIENT));
+ return true;
+}
+
+bool
+Proxy::InitChannel(FNET_Channel *, uint32_t)
+{
+ return false;
+}
+
+// ---------------------------------------------
+
+int
+Proxy::Main()
+{
+ FNET_SignalShutDown::hookSignals();
+ if (_argc != 3) {
+ fprintf(stderr, "usage: %s <listen spec> <target spec>\n", _argv[0]);
+ return 1;
+ }
+
+ FNET_Connector *listener =
+ _transport.Listen(_argv[1], this, this);
+ if (listener != NULL)
+ listener->SubRef();
+
+ _transport.SetLogStats(true);
+ FNET_SignalShutDown ssd(_transport);
+ _transport.Main();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ Proxy myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/examples/test/.gitignore b/fnet/src/examples/test/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/examples/test/.gitignore
diff --git a/fnet/src/examples/timeout/.gitignore b/fnet/src/examples/timeout/.gitignore
new file mode 100644
index 00000000000..017989a129b
--- /dev/null
+++ b/fnet/src/examples/timeout/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+timeout
+fnet_timeout_app
diff --git a/fnet/src/examples/timeout/CMakeLists.txt b/fnet/src/examples/timeout/CMakeLists.txt
new file mode 100644
index 00000000000..5b9514ab0c8
--- /dev/null
+++ b/fnet/src/examples/timeout/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_timeout_app
+ SOURCES
+ timeout.cpp
+ INSTALL bin
+ DEPENDS
+ fnet
+)
diff --git a/fnet/src/examples/timeout/timeout.cpp b/fnet/src/examples/timeout/timeout.cpp
new file mode 100644
index 00000000000..39bfb96eb0f
--- /dev/null
+++ b/fnet/src/examples/timeout/timeout.cpp
@@ -0,0 +1,93 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP("timeout");
+#include <vespa/fnet/fnet.h>
+
+
+class Timeout : public FNET_Task
+{
+private:
+ FNET_PacketQueue *_queue;
+
+ Timeout(const Timeout &);
+ Timeout &operator=(const Timeout &);
+
+public:
+ Timeout(FNET_Scheduler *scheduler,
+ FNET_PacketQueue *queue)
+ : FNET_Task(scheduler),
+ _queue(queue)
+ {}
+
+ virtual void PerformTask();
+};
+
+
+void
+Timeout::PerformTask()
+{
+ _queue->QueuePacket(&FNET_ControlPacket::Timeout, FNET_Context());
+}
+
+
+class MyApp : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+
+int
+MyApp::Main()
+{
+ double ms;
+ FastOS_Time t;
+ FNET_PacketQueue queue;
+ FastOS_ThreadPool pool(65000);
+ FNET_Transport transport;
+ Timeout timeout(transport.GetScheduler(), &queue);
+ transport.Start(&pool);
+
+ // stable-state operation
+ FastOS_Thread::Sleep(500);
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ fprintf(stderr, "scheduling timeout in 2 seconds...\n");
+ t.SetNow();
+ timeout.Schedule(2.0); // timeout in 2 seconds
+
+ FastOS_Thread::Sleep(1000);
+
+ timeout.Unschedule(); // cancel timeout
+ ms = t.MilliSecsToNow();
+
+ if (queue.GetPacketCnt_NoLock() == 0)
+ fprintf(stderr, "timeout canceled; no timeout packet delivered\n");
+ fprintf(stderr, "time since timeout was scheduled: %f ms\n", ms);
+
+ fprintf(stderr, "scheduling timeout in 2 seconds...\n");
+ t.SetNow();
+ timeout.Schedule(2.0); // timeout in 2 seconds
+
+ packet = queue.DequeuePacket(&context); // wait for timeout
+ ms = t.MilliSecsToNow();
+
+ if (packet->IsTimeoutCMD())
+ fprintf(stderr, "got timeout packet\n");
+ fprintf(stderr, "time since timeout was scheduled: %f ms\n", ms);
+
+ transport.ShutDown(true);
+ pool.Close();
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ MyApp myapp;
+ return myapp.Entry(argc, argv);
+}
diff --git a/fnet/src/testlist.txt b/fnet/src/testlist.txt
new file mode 100644
index 00000000000..5932feee656
--- /dev/null
+++ b/fnet/src/testlist.txt
@@ -0,0 +1,17 @@
+tests/connect_thread
+tests/connection_spread
+tests/databuffer
+tests/examples
+tests/fdselector
+tests/frt/memorytub
+tests/frt/method_pt
+tests/frt/parallel_rpc
+tests/frt/rpc
+tests/frt/values
+tests/info
+tests/locking
+tests/printstuff
+tests/scheduling
+tests/sync_execute
+tests/thread_selection
+tests/time
diff --git a/fnet/src/tests/.gitignore b/fnet/src/tests/.gitignore
new file mode 100644
index 00000000000..a3e9c375723
--- /dev/null
+++ b/fnet/src/tests/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+*_test
diff --git a/fnet/src/tests/connect_thread/.gitignore b/fnet/src/tests/connect_thread/.gitignore
new file mode 100644
index 00000000000..66bba07002d
--- /dev/null
+++ b/fnet/src/tests/connect_thread/.gitignore
@@ -0,0 +1 @@
+fnet_connect_thread_test_app
diff --git a/fnet/src/tests/connect_thread/CMakeLists.txt b/fnet/src/tests/connect_thread/CMakeLists.txt
new file mode 100644
index 00000000000..535ac5e3561
--- /dev/null
+++ b/fnet/src/tests/connect_thread/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_connect_thread_test_app
+ SOURCES
+ connect_thread_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_connect_thread_test_app COMMAND fnet_connect_thread_test_app)
diff --git a/fnet/src/tests/connect_thread/connect_thread_test.cpp b/fnet/src/tests/connect_thread/connect_thread_test.cpp
new file mode 100644
index 00000000000..f8492d147a6
--- /dev/null
+++ b/fnet/src/tests/connect_thread/connect_thread_test.cpp
@@ -0,0 +1,26 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/connect_thread.h>
+
+struct MyConn : public fnet::ExtConnectable {
+ bool connected = false;
+ void ext_connect() override { connected = true; }
+};
+
+TEST("require that connect thread will connect stuff") {
+ std::vector<MyConn> conns(5);
+ {
+ fnet::ConnectThread thread;
+ thread.connect_later(&conns[0]);
+ thread.connect_later(&conns[2]);
+ thread.connect_later(&conns[4]);
+ }
+ EXPECT_TRUE(conns[0].connected);
+ EXPECT_TRUE(!conns[1].connected);
+ EXPECT_TRUE(conns[2].connected);
+ EXPECT_TRUE(!conns[3].connected);
+ EXPECT_TRUE(conns[4].connected);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/connection_spread/.gitignore b/fnet/src/tests/connection_spread/.gitignore
new file mode 100644
index 00000000000..34408abbf7c
--- /dev/null
+++ b/fnet/src/tests/connection_spread/.gitignore
@@ -0,0 +1 @@
+fnet_connection_spread_test_app
diff --git a/fnet/src/tests/connection_spread/CMakeLists.txt b/fnet/src/tests/connection_spread/CMakeLists.txt
new file mode 100644
index 00000000000..9ed541b62e3
--- /dev/null
+++ b/fnet/src/tests/connection_spread/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_connection_spread_test_app
+ SOURCES
+ connection_spread_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_connection_spread_test_app COMMAND fnet_connection_spread_test_app)
diff --git a/fnet/src/tests/connection_spread/connection_spread_test.cpp b/fnet/src/tests/connection_spread/connection_spread_test.cpp
new file mode 100644
index 00000000000..a10278daf32
--- /dev/null
+++ b/fnet/src/tests/connection_spread/connection_spread_test.cpp
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <thread>
+#include <chrono>
+#include <set>
+
+using namespace std::literals;
+
+struct DummyAdapter : FNET_IServerAdapter {
+ bool InitAdminChannel(FNET_Channel *) override { return false; }
+ bool InitChannel(FNET_Channel *, uint32_t) override { return false; }
+};
+
+struct DummyStreamer : FNET_IPacketStreamer {
+ bool GetPacketInfo(FNET_DataBuffer *, uint32_t *, uint32_t *, uint32_t *, bool *) override { return false; }
+ FNET_Packet *Decode(FNET_DataBuffer *, uint32_t, uint32_t, FNET_Context) override { return nullptr; }
+ void Encode(FNET_Packet *, uint32_t, FNET_DataBuffer *) override {}
+};
+
+struct Fixture {
+ DummyStreamer streamer;
+ DummyAdapter adapter;
+ FastOS_ThreadPool thread_pool;
+ FNET_Transport client;
+ FNET_Transport server;
+ Fixture() : streamer(), adapter(), thread_pool(128 * 1024), client(8), server(8)
+ {
+ ASSERT_TRUE(client.Start(&thread_pool));
+ ASSERT_TRUE(server.Start(&thread_pool));
+ }
+ void wait_for_components(size_t client_cnt, size_t server_cnt) {
+ bool ok = false;
+ for (size_t i = 0; !ok && (i < 10000); ++i) {
+ std::this_thread::sleep_for(3ms);
+ ok = ((client.GetNumIOComponents() == client_cnt) &&
+ (server.GetNumIOComponents() == server_cnt));
+ }
+ EXPECT_EQUAL(client.GetNumIOComponents(), client_cnt);
+ EXPECT_EQUAL(server.GetNumIOComponents(), server_cnt);
+ }
+ ~Fixture() {
+ server.ShutDown(true);
+ client.ShutDown(true);
+ thread_pool.Close();
+ }
+};
+
+void check_threads(FNET_Transport &transport, size_t num_threads, const vespalib::string &tag) {
+ std::set<FNET_TransportThread *> threads;
+ while (threads.size() < num_threads) {
+ threads.insert(transport.select_thread(nullptr, 0));
+ }
+ for (auto thread: threads) {
+ uint32_t cnt = thread->GetNumIOComponents();
+ fprintf(stderr, "-- %s thread: %u io components\n", tag.c_str(), cnt);
+ EXPECT_GREATER(cnt, 1u);
+ }
+}
+
+TEST_F("require that connections are spread among transport threads", Fixture)
+{
+ FNET_Connector *listener = f1.server.Listen("tcp/0", &f1.streamer, &f1.adapter);
+ ASSERT_TRUE(listener);
+ uint32_t port = listener->GetPortNumber();
+ vespalib::string spec = vespalib::make_string("tcp/localhost:%u", port);
+ std::vector<FNET_Connection *> connections;
+ for (size_t i = 0; i < 256; ++i) {
+ std::this_thread::sleep_for(1ms);
+ connections.push_back(f1.client.Connect(spec.c_str(), &f1.streamer));
+ ASSERT_TRUE(connections.back());
+ }
+ f1.wait_for_components(256, 257);
+ check_threads(f1.client, 8, "client");
+ check_threads(f1.server, 8, "server");
+ listener->SubRef();
+ for (FNET_Connection *conn: connections) {
+ conn->SubRef();
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/databuffer/.gitignore b/fnet/src/tests/databuffer/.gitignore
new file mode 100644
index 00000000000..b9c5810fe42
--- /dev/null
+++ b/fnet/src/tests/databuffer/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+databuffer_test
+fnet_databuffer_test_app
diff --git a/fnet/src/tests/databuffer/CMakeLists.txt b/fnet/src/tests/databuffer/CMakeLists.txt
new file mode 100644
index 00000000000..47122e11d96
--- /dev/null
+++ b/fnet/src/tests/databuffer/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_databuffer_test_app
+ SOURCES
+ databuffer.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_databuffer_test_app COMMAND fnet_databuffer_test_app)
diff --git a/fnet/src/tests/databuffer/DESC b/fnet/src/tests/databuffer/DESC
new file mode 100644
index 00000000000..d947b059899
--- /dev/null
+++ b/fnet/src/tests/databuffer/DESC
@@ -0,0 +1,2 @@
+Benchmark some databuffer operations and check for consistent
+encoding/decoding.
diff --git a/fnet/src/tests/databuffer/FILES b/fnet/src/tests/databuffer/FILES
new file mode 100644
index 00000000000..c0265d2742f
--- /dev/null
+++ b/fnet/src/tests/databuffer/FILES
@@ -0,0 +1 @@
+databuffer.cpp
diff --git a/fnet/src/tests/databuffer/databuffer.cpp b/fnet/src/tests/databuffer/databuffer.cpp
new file mode 100644
index 00000000000..7aa75a95e65
--- /dev/null
+++ b/fnet/src/tests/databuffer/databuffer.cpp
@@ -0,0 +1,201 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/databuffer.h>
+
+TEST("test resetIfEmpty") {
+ FNET_DataBuffer buf(64);
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_TRUE(buf.GetData() == buf.GetFree());
+ buf.WriteInt32(11111111);
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_FALSE(buf.GetData() == buf.GetFree());
+ buf.resetIfEmpty();
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_FALSE(buf.GetData() == buf.GetFree());
+ EXPECT_EQUAL(11111111u, buf.ReadInt32());
+ buf.resetIfEmpty();
+ EXPECT_TRUE(buf.GetData() == buf.GetDead());
+ EXPECT_TRUE(buf.GetData() == buf.GetFree());
+}
+
+TEST("testResize") {
+ FNET_DataBuffer buf(64);
+ uint32_t initialSize = buf.GetBufSize();
+ buf.WriteInt32(11111111);
+ buf.WriteInt32(22222222);
+ buf.WriteInt32(33333333);
+ buf.WriteInt32(44444444);
+ buf.WriteInt32(55555555);
+ EXPECT_TRUE(buf.ReadInt32() == 11111111);
+ buf.EnsureFree(initialSize);
+ EXPECT_TRUE(buf.GetBufSize() > initialSize);
+ EXPECT_TRUE(buf.ReadInt32() == 22222222);
+ EXPECT_TRUE(!buf.Shrink(buf.GetBufSize()));
+ EXPECT_TRUE(!buf.Shrink(buf.GetBufSize() + 16));
+ EXPECT_TRUE(!buf.Shrink(2 * 4));
+ EXPECT_TRUE(buf.Shrink(3 * 4));
+ EXPECT_TRUE(buf.GetBufSize() == 3 * 4);
+ EXPECT_TRUE(buf.ReadInt32() == 33333333);
+ buf.WriteInt32(66666666);
+ buf.EnsureFree(16);
+ EXPECT_TRUE(buf.GetDataLen() == 3 * 4);
+ EXPECT_TRUE(buf.GetBufSize() >= 16 + 3 * 4);
+ EXPECT_TRUE(buf.ReadInt32() == 44444444);
+ EXPECT_TRUE(buf.ReadInt32() == 55555555);
+ EXPECT_TRUE(buf.ReadInt32() == 66666666);
+ EXPECT_TRUE(buf.Shrink(0));
+ EXPECT_TRUE(buf.GetBufSize() == 0);
+ buf.WriteInt32(42);
+ EXPECT_TRUE(buf.GetBufSize() >= 4);
+ EXPECT_TRUE(buf.ReadInt32() == 42);
+ EXPECT_TRUE(buf.GetDataLen() == 0);
+}
+
+TEST("testSpeed") {
+ FNET_DataBuffer buf0(20000);
+ FNET_DataBuffer buf1(20000);
+ FNET_DataBuffer buf2(20000);
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ int i;
+ int k;
+
+ // fill buf0 with random data
+ for (i = 0; i < 16000; i++) {
+ buf0.WriteInt8((uint8_t)rand());
+ }
+
+ // copy buf0 into buf1
+ for (i = 0; i < 16000; i++) {
+ buf1.WriteInt8(buf0.ReadInt8());
+ }
+
+ // undo read from buf0
+ buf0.DeadToData(buf0.GetDeadLen());
+
+ // test encode/decode speed
+ start.SetNow();
+
+ for (i = 0; i < 5000; i++) {
+ buf2.Clear();
+ for (k = 0; k < 500; k++) {
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt16(buf1.ReadInt16());
+ buf2.WriteInt8(buf1.ReadInt8());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt16(buf1.ReadInt16());
+ buf2.WriteInt32(buf1.ReadInt32());
+ buf2.WriteInt64(buf1.ReadInt64());
+ buf2.WriteInt32(buf1.ReadInt32());
+ }
+ buf1.Clear();
+ for (k = 0; k < 500; k++) {
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt16(buf2.ReadInt16());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt64(buf2.ReadInt64());
+ buf1.WriteInt32(buf2.ReadInt32());
+ buf1.WriteInt8(buf2.ReadInt8());
+ buf1.WriteInt16(buf2.ReadInt16());
+ buf1.WriteInt32(buf2.ReadInt32());
+ }
+ }
+ buf2.DeadToData(buf2.GetDeadLen());
+ stop.SetNow();
+
+ stop -= start;
+ fprintf(stderr, "encode/decode time (~160MB): %1.2f\n", stop.MilliSecs());
+
+ EXPECT_TRUE(buf0.Equals(&buf1) && buf0.Equals(&buf2));
+
+ // test encode[fast]/decode speed
+ start.SetNow();
+
+ for (i = 0; i < 5000; i++) {
+ buf2.Clear();
+ for (k = 0; k < 500; k++) {
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt16Fast(buf1.ReadInt16());
+ buf2.WriteInt8Fast(buf1.ReadInt8());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt16Fast(buf1.ReadInt16());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ buf2.WriteInt64Fast(buf1.ReadInt64());
+ buf2.WriteInt32Fast(buf1.ReadInt32());
+ }
+ buf1.Clear();
+ for (k = 0; k < 500; k++) {
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt16Fast(buf2.ReadInt16());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt64Fast(buf2.ReadInt64());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ buf1.WriteInt8Fast(buf2.ReadInt8());
+ buf1.WriteInt16Fast(buf2.ReadInt16());
+ buf1.WriteInt32Fast(buf2.ReadInt32());
+ }
+ }
+ buf2.DeadToData(buf2.GetDeadLen());
+ stop.SetNow();
+
+ stop -= start;
+ fprintf(stderr, "encode[fast]/decode time (~160MB): %1.2f\n", stop.MilliSecs());
+
+ EXPECT_TRUE(buf0.Equals(&buf1) && buf0.Equals(&buf2));
+
+ // init source table for table streaming test
+ uint32_t table[4000];
+ for (i = 0; i < 4000; i++) {
+ table[i] = i;
+ }
+
+ // test byte-swap table encoding speed
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+ buf1.Clear();
+ for (k = 0; k < 4000; k += 8) {
+ buf1.WriteInt32Fast(table[k]);
+ buf1.WriteInt32Fast(table[k + 1]);
+ buf1.WriteInt32Fast(table[k + 2]);
+ buf1.WriteInt32Fast(table[k + 3]);
+ buf1.WriteInt32Fast(table[k + 4]);
+ buf1.WriteInt32Fast(table[k + 5]);
+ buf1.WriteInt32Fast(table[k + 6]);
+ buf1.WriteInt32Fast(table[k + 7]);
+ }
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "byte-swap array encoding[fast] (~160 MB): %1.2f ms\n",
+ stop.MilliSecs());
+
+ // test direct-copy table encoding speed
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+ buf2.Clear();
+ buf2.EnsureFree(16000);
+ memcpy(buf2.GetFree(), table, 16000);
+ buf2.FreeToData(16000);
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct-copy array encoding (~160 MB): %1.2f ms\n",
+ stop.MilliSecs());
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/examples/.gitignore b/fnet/src/tests/examples/.gitignore
new file mode 100644
index 00000000000..a3c2ba8686c
--- /dev/null
+++ b/fnet/src/tests/examples/.gitignore
@@ -0,0 +1 @@
+fnet_examples_test_app
diff --git a/fnet/src/tests/examples/CMakeLists.txt b/fnet/src/tests/examples/CMakeLists.txt
new file mode 100644
index 00000000000..79f9047f626
--- /dev/null
+++ b/fnet/src/tests/examples/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_examples_test_app
+ SOURCES
+ examples_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_examples_test_app NO_VALGRIND COMMAND fnet_examples_test_app)
diff --git a/fnet/src/tests/examples/FILES b/fnet/src/tests/examples/FILES
new file mode 100644
index 00000000000..58573211940
--- /dev/null
+++ b/fnet/src/tests/examples/FILES
@@ -0,0 +1 @@
+examples_test.cpp
diff --git a/fnet/src/tests/examples/examples_test.cpp b/fnet/src/tests/examples/examples_test.cpp
new file mode 100644
index 00000000000..5debd969a60
--- /dev/null
+++ b/fnet/src/tests/examples/examples_test.cpp
@@ -0,0 +1,246 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/slaveproc.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/vespalib/util/thread.h>
+
+// reserved in vespa/factory/doc/port-ranges.txt
+static const int PORT0 = 18570;
+static const int PORT1 = 18571;
+static const int PORT2 = 18572;
+static const int PORT3 = 18573;
+static const int PORT4 = 18574;
+static const int PORT5 = 18575;
+static const int PORT6 = 18576;
+static const int PORT7 = 18577;
+static const int PORT8 = 18578;
+static const int PORT9 = 18579;
+
+using vespalib::SlaveProc;
+
+bool runProc(SlaveProc &proc, bool &done) {
+ char buf[4096];
+ proc.close(); // close stdin
+ while (proc.running() && !done) {
+ if (!proc.eof()) {
+ uint32_t res = proc.read(buf, sizeof(buf), 10);
+ std::string tmp(buf, res);
+ fprintf(stderr, "%s", tmp.c_str());
+ }
+ vespalib::Thread::sleep(10);
+ }
+ if (done && proc.running()) {
+ kill(proc.getPid(), SIGTERM);
+ return proc.wait(60000);
+ }
+ return !proc.failed();
+}
+
+bool runProc(const std::string &cmd) {
+ bool ok = false;
+ for (size_t retry = 0; !ok && retry < 60; ++retry) {
+ if (retry > 0) {
+ fprintf(stderr, "retrying command in 500ms...\n");
+ vespalib::Thread::sleep(500);
+ }
+ bool done = false;
+ SlaveProc proc(cmd.c_str());
+ ok = runProc(proc, done);
+ }
+ return ok;
+}
+
+TEST("usage") {
+ bool done = false;
+ {
+ SlaveProc proc("../../examples/proxy/fnet_proxy_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/ping/fnet_pingserver_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/ping/fnet_pingclient_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_server_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_echo_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_info");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_invoke");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_server_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/fnet_rpc_callback_client_app");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+ {
+ SlaveProc proc("../../examples/frt/rpc/rpc_proxy");
+ EXPECT_FALSE(runProc(proc, done));
+ }
+}
+
+TEST("timeout") {
+ std::string out;
+ EXPECT_TRUE(SlaveProc::run("../../examples/timeout/fnet_timeout_app", out));
+ fprintf(stderr, "%s\n", out.c_str());
+}
+
+TEST_MT_F("ping", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("ping times out", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("ping with proxy", 3, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/ping/fnet_pingserver_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else if (thread_id == 1) {
+ SlaveProc proc(vespalib::make_string("../../examples/proxy/fnet_proxy_app tcp/%d tcp/localhost:%d",
+ PORT1, PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/ping/fnet_pingclient_app tcp/localhost:%d",
+ PORT1).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc client server", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc echo client", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_echo_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc info", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d",
+ PORT0).c_str()));
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_info tcp/localhost:%d verbose",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc invoke", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/rpc_invoke tcp/localhost:%d frt.rpc.echo "
+ "b:1 h:2 i:4 l:8 f:0.5 d:0.25 s:foo",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc callback client server", 2, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d",
+ PORT0).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MT_F("rpc callback client server with proxy", 3, bool()) {
+ if (thread_id == 0) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_server_app tcp/%d",
+ PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else if (thread_id == 1) {
+ SlaveProc proc(vespalib::make_string("../../examples/frt/rpc/rpc_proxy tcp/%d tcp/localhost:%d",
+ PORT1, PORT0).c_str());
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(proc, f1));
+ } else {
+ TEST_BARRIER();
+ EXPECT_TRUE(runProc(vespalib::make_string("../../examples/frt/rpc/fnet_rpc_callback_client_app tcp/localhost:%d",
+ PORT1).c_str()));
+ f1 = true;
+ }
+}
+
+TEST_MAIN_WITH_PROCESS_PROXY() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/fdselector/.gitignore b/fnet/src/tests/fdselector/.gitignore
new file mode 100644
index 00000000000..68a9dea5652
--- /dev/null
+++ b/fnet/src/tests/fdselector/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+fdselector_test
+fnet_fdselector_test_app
diff --git a/fnet/src/tests/fdselector/CMakeLists.txt b/fnet/src/tests/fdselector/CMakeLists.txt
new file mode 100644
index 00000000000..149d3642983
--- /dev/null
+++ b/fnet/src/tests/fdselector/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_fdselector_test_app
+ SOURCES
+ fdselector.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_fdselector_test_app COMMAND fnet_fdselector_test_app)
diff --git a/fnet/src/tests/fdselector/DESC b/fnet/src/tests/fdselector/DESC
new file mode 100644
index 00000000000..050e2a746d1
--- /dev/null
+++ b/fnet/src/tests/fdselector/DESC
@@ -0,0 +1 @@
+Test selecting on external file descriptors in the FNET event loop.
diff --git a/fnet/src/tests/fdselector/FILES b/fnet/src/tests/fdselector/FILES
new file mode 100644
index 00000000000..d03840ead52
--- /dev/null
+++ b/fnet/src/tests/fdselector/FILES
@@ -0,0 +1 @@
+fdselector.cpp
diff --git a/fnet/src/tests/fdselector/fdselector.cpp b/fnet/src/tests/fdselector/fdselector.cpp
new file mode 100644
index 00000000000..5af2716c0d4
--- /dev/null
+++ b/fnet/src/tests/fdselector/fdselector.cpp
@@ -0,0 +1,220 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+
+struct Handler : public FNET_IFDSelectorHandler
+{
+ int readEventCnt[2];
+ int writeEventCnt[2];
+
+ Handler()
+ : readEventCnt(),
+ writeEventCnt()
+ {
+ reset();
+ }
+ void readEvent(FNET_FDSelector *src)
+ {
+ readEventCnt[src->getContext()._value.INT]++;
+ }
+ void writeEvent(FNET_FDSelector *src)
+ {
+ writeEventCnt[src->getContext()._value.INT]++;
+ }
+ bool empty()
+ {
+ return ( readEventCnt[0] == 0
+ && readEventCnt[1] == 0
+ && writeEventCnt[0] == 0
+ && writeEventCnt[1] == 0);
+ }
+ void reset()
+ {
+ readEventCnt[0] = 0;
+ readEventCnt[1] = 0;
+ writeEventCnt[0] = 0;
+ writeEventCnt[1] = 0;
+ }
+};
+
+
+struct State
+{
+ int pipefd[2];
+ FNET_Transport transport;
+ Handler handler;
+
+ void eventLoop(int cnt)
+ {
+ for (int i = 0; i < cnt; ++i) {
+ transport.EventLoopIteration();
+ }
+ }
+ bool checkEmpty()
+ {
+ eventLoop(1);
+ return handler.empty();
+ }
+ void shutDown()
+ {
+ transport.ShutDown(false);
+ for (;;) {
+ if (!transport.EventLoopIteration()) {
+ return;
+ }
+ }
+ }
+ State() : pipefd(), transport(), handler() {
+ pipefd[0] = -1;
+ pipefd[1] = -1;
+ ASSERT_TRUE(pipe(pipefd) == 0);
+ ASSERT_TRUE(transport.InitEventLoop());
+ ASSERT_TRUE(handler.empty());
+ }
+ ~State() {
+ shutDown();
+ }
+};
+
+
+struct Selector : public FNET_FDSelector
+{
+ static FNET_Mutex mutex;
+ static int ctorCnt;
+ static int dtorCnt;
+
+ Selector(State &state, uint32_t idx)
+ : FNET_FDSelector(&state.transport, state.pipefd[idx],
+ &state.handler, FNET_Context(idx))
+ {
+ mutex.Lock();
+ ctorCnt++;
+ mutex.Unlock();
+ }
+ ~Selector()
+ {
+ mutex.Lock();
+ dtorCnt++;
+ mutex.Unlock();
+ }
+};
+
+FNET_Mutex Selector::mutex;
+int Selector::ctorCnt = 0;
+int Selector::dtorCnt = 0;
+
+
+TEST_F("testEmptySelection", State()) {
+ State &state = f1;
+ Selector *sel_0 = new Selector(state, 0);
+ Selector *sel_1 = new Selector(state, 1);
+
+ state.eventLoop(5);
+ EXPECT_TRUE(state.handler.empty());
+
+ sel_0->dispose();
+ sel_1->dispose();
+}
+
+
+TEST_F("testWriteEvent", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testReadEvent", State()) {
+ State &state = f1;
+ char buf[16];
+ char buf2[16];
+ strcpy(buf, "test");
+ strcpy(buf2, "bogus");
+
+ Selector *sel = new Selector(state, 0);
+
+ sel->updateReadSelection(true);
+ EXPECT_TRUE(state.checkEmpty());
+ EXPECT_TRUE(state.checkEmpty());
+ EXPECT_TRUE(state.checkEmpty());
+
+ int res = write(state.pipefd[1], buf, 5);
+ EXPECT_TRUE(res == 5);
+
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.readEventCnt[0] > 7);
+ state.handler.readEventCnt[0] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ res = read(state.pipefd[0], buf2, 10);
+ EXPECT_TRUE(res == 5);
+ EXPECT_TRUE(strcmp(buf, buf2) == 0);
+
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.readEventCnt[0] < 4);
+ state.handler.readEventCnt[0] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testDispose", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+
+TEST_F("testToggleEvent", State()) {
+ State &state = f1;
+ Selector *sel = new Selector(state, 1);
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->updateWriteSelection(false);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] < 4);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->updateWriteSelection(true);
+ state.eventLoop(10);
+ EXPECT_TRUE(state.handler.writeEventCnt[1] > 7);
+ state.handler.writeEventCnt[1] = 0;
+ EXPECT_TRUE(state.handler.empty());
+
+ sel->dispose();
+ EXPECT_TRUE(state.checkEmpty());
+}
+
+TEST_MAIN() {
+ ASSERT_TRUE(Selector::ctorCnt == 0);
+ ASSERT_TRUE(Selector::dtorCnt == 0);
+ TEST_RUN_ALL();
+ EXPECT_TRUE(Selector::ctorCnt > 0);
+ EXPECT_TRUE(Selector::dtorCnt > 0);
+ EXPECT_TRUE(Selector::ctorCnt == Selector::dtorCnt);
+}
diff --git a/fnet/src/tests/frt/memorytub/.gitignore b/fnet/src/tests/frt/memorytub/.gitignore
new file mode 100644
index 00000000000..e61f6585695
--- /dev/null
+++ b/fnet/src/tests/frt/memorytub/.gitignore
@@ -0,0 +1,6 @@
+*.core
+.depend
+Makefile
+core
+memorytub_test
+fnet_memorytub_test_app
diff --git a/fnet/src/tests/frt/memorytub/CMakeLists.txt b/fnet/src/tests/frt/memorytub/CMakeLists.txt
new file mode 100644
index 00000000000..1b6aa6e778b
--- /dev/null
+++ b/fnet/src/tests/frt/memorytub/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_memorytub_test_app
+ SOURCES
+ memorytub.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_memorytub_test_app COMMAND fnet_memorytub_test_app)
diff --git a/fnet/src/tests/frt/memorytub/DESC b/fnet/src/tests/frt/memorytub/DESC
new file mode 100644
index 00000000000..b075fd81de5
--- /dev/null
+++ b/fnet/src/tests/frt/memorytub/DESC
@@ -0,0 +1 @@
+Test the memorytub class.
diff --git a/fnet/src/tests/frt/memorytub/FILES b/fnet/src/tests/frt/memorytub/FILES
new file mode 100644
index 00000000000..58e3592a833
--- /dev/null
+++ b/fnet/src/tests/frt/memorytub/FILES
@@ -0,0 +1 @@
+memorytub.cpp
diff --git a/fnet/src/tests/frt/memorytub/memorytub.cpp b/fnet/src/tests/frt/memorytub/memorytub.cpp
new file mode 100644
index 00000000000..a6be26bc860
--- /dev/null
+++ b/fnet/src/tests/frt/memorytub/memorytub.cpp
@@ -0,0 +1,124 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+
+//---------------------------------------------------------------
+
+enum {
+ SMALL_ALLOCS = 90,
+ BIG_ALLOCS = 10,
+ ALLOCS = SMALL_ALLOCS + BIG_ALLOCS,
+ SMALL_SIZE = 407,
+ BIG_SIZE = 40700,
+ NOID = 99999
+};
+
+//---------------------------------------------------------------
+
+struct Fixture {
+ FRT_MemoryTub _tub;
+ char *_res[ALLOCS];
+ uint32_t _i;
+ uint32_t _j;
+ Fixture() : _tub(), _res(), _i(0), _j(0) {}
+ bool overlap(char *start1, char *end1,
+ char *start2, char *end2);
+ bool inTub(char *pt, char *end);
+ bool notInTub(char *pt, char *end);
+};
+
+//---------------------------------------------------------------
+
+bool
+Fixture::overlap(char *start1, char *end1,
+ char *start2, char *end2)
+{
+ if (start1 == end1)
+ return false;
+
+ if (start2 == end2)
+ return false;
+
+ if (start2 >= start1 && start2 < end1)
+ return true;
+
+ if (end2 > start1 && end2 <= end1)
+ return true;
+
+ if (start1 >= start2 && start1 < end2)
+ return true;
+
+ if (end1 > start2 && end1 <= end2)
+ return true;
+
+ return false;
+}
+
+
+bool
+Fixture::inTub(char *pt, char *end)
+{
+ for (char *p = pt; p < end; p++)
+ if (!_tub.InTub(p))
+ return false;
+ return true;
+}
+
+
+bool
+Fixture::notInTub(char *pt, char *end)
+{
+ for (char *p = pt; p < end; p++)
+ if (_tub.InTub(p))
+ return false;
+ return true;
+}
+
+//---------------------------------------------------------------
+
+TEST_F("memory tub", Fixture()) {
+ for(f1._i = 0; f1._i < ALLOCS; f1._i++)
+ f1._res[f1._i] = NULL;
+ f1._i = NOID;
+ f1._j = NOID;
+
+ EXPECT_TRUE(!f1._tub.InTub(&f1._tub));
+ EXPECT_TRUE((uint32_t)SMALL_SIZE < (uint32_t)FRT_MemoryTub::ALLOC_LIMIT);
+ EXPECT_TRUE((uint32_t)BIG_SIZE > (uint32_t)FRT_MemoryTub::ALLOC_LIMIT);
+ EXPECT_TRUE((SMALL_SIZE * SMALL_ALLOCS)
+ > (FRT_MemoryTub::FIXED_SIZE + FRT_MemoryTub::CHUNK_SIZE));
+ TEST_FLUSH();
+
+ for (f1._i = 0; f1._i < ALLOCS; f1._i++) {
+ uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE;
+
+ f1._res[f1._i] = (char *) f1._tub.Alloc(size_i);
+ EXPECT_TRUE(((void *)f1._res[f1._i]) != ((void *)&f1._tub));
+ memset(f1._res[f1._i], 0x55, size_i);
+ EXPECT_TRUE(f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i));
+ }
+ TEST_FLUSH();
+
+ for (f1._i = 0; f1._i < ALLOCS; f1._i++) {
+ uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE;
+ EXPECT_TRUE(f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i));
+
+ for (f1._j = f1._i + 1; f1._j < ALLOCS; f1._j++) {
+ uint32_t size_j = f1._j < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE;
+ EXPECT_TRUE(!f1.overlap(f1._res[f1._i], f1._res[f1._i] + size_i,
+ f1._res[f1._j], f1._res[f1._j] + size_j));
+ }
+ }
+ TEST_FLUSH();
+
+ f1._tub.Reset();
+ f1._j = NOID;
+
+ for (f1._i = 0; f1._i < ALLOCS; f1._i++) {
+ uint32_t size_i = f1._i < SMALL_ALLOCS ? SMALL_SIZE : BIG_SIZE;
+ EXPECT_TRUE(!f1.inTub(f1._res[f1._i], f1._res[f1._i] + size_i));
+ }
+ TEST_FLUSH();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/method_pt/.gitignore b/fnet/src/tests/frt/method_pt/.gitignore
new file mode 100644
index 00000000000..5bb3f455d1d
--- /dev/null
+++ b/fnet/src/tests/frt/method_pt/.gitignore
@@ -0,0 +1,6 @@
+*.core
+.depend
+Makefile
+core
+method_pt_test
+fnet_method_pt_test_app
diff --git a/fnet/src/tests/frt/method_pt/CMakeLists.txt b/fnet/src/tests/frt/method_pt/CMakeLists.txt
new file mode 100644
index 00000000000..d5a9566dbba
--- /dev/null
+++ b/fnet/src/tests/frt/method_pt/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_method_pt_test_app
+ SOURCES
+ method_pt.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_method_pt_test_app COMMAND fnet_method_pt_test_app)
diff --git a/fnet/src/tests/frt/method_pt/DESC b/fnet/src/tests/frt/method_pt/DESC
new file mode 100644
index 00000000000..81e7d2c6d1c
--- /dev/null
+++ b/fnet/src/tests/frt/method_pt/DESC
@@ -0,0 +1,2 @@
+Ensure that the method pointer magic used by FRT works with the
+current compiler.
diff --git a/fnet/src/tests/frt/method_pt/FILES b/fnet/src/tests/frt/method_pt/FILES
new file mode 100644
index 00000000000..9586cd113fb
--- /dev/null
+++ b/fnet/src/tests/frt/method_pt/FILES
@@ -0,0 +1 @@
+method_pt.cpp
diff --git a/fnet/src/tests/frt/method_pt/method_pt.cpp b/fnet/src/tests/frt/method_pt/method_pt.cpp
new file mode 100644
index 00000000000..539be6846a9
--- /dev/null
+++ b/fnet/src/tests/frt/method_pt/method_pt.cpp
@@ -0,0 +1,395 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/fnet/frt/frt.h>
+
+class Test;
+class SimpleHandler;
+
+class MediumHandler1;
+class MediumHandler2;
+class MediumHandler3;
+
+class ComplexHandler1;
+class ComplexHandler2;
+class ComplexHandler3;
+
+//-------------------------------------------------------------
+
+Test *_test;
+
+FRT_Supervisor *_supervisor;
+FRT_Target *_target;
+SimpleHandler *_simpleHandler;
+MediumHandler1 *_mediumHandler1;
+MediumHandler2 *_mediumHandler2;
+MediumHandler3 *_mediumHandler3;
+ComplexHandler1 *_complexHandler1;
+ComplexHandler2 *_complexHandler2;
+ComplexHandler3 *_complexHandler3;
+
+bool _mediumHandlerOK;
+bool _complexHandlerOK;
+
+//-------------------------------------------------------------
+
+class MediumA
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~MediumA(void) { }
+
+ virtual void foo() = 0;
+};
+
+
+class MediumB
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~MediumB(void) { }
+
+ virtual void bar() = 0;
+};
+
+//-------------------------------------------------------------
+
+class ComplexA
+{
+private:
+ uint32_t _fill1;
+ uint32_t _fill2;
+ uint32_t _fill3;
+
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~ComplexA(void) { }
+
+ ComplexA() : _fill1(1), _fill2(2), _fill3(3) {}
+ virtual void foo() {}
+};
+
+
+class ComplexB
+{
+private:
+ uint32_t _fill1;
+ uint32_t _fill2;
+ uint32_t _fill3;
+
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~ComplexB(void) { }
+
+ ComplexB() : _fill1(1), _fill2(2), _fill3(3) {}
+ virtual void bar() {}
+};
+
+//-------------------------------------------------------------
+
+class SimpleHandler : public FRT_Invokable
+{
+public:
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+//-------------------------------------------------------------
+
+class MediumHandler1 : public FRT_Invokable,
+ public MediumA,
+ public MediumB
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+
+class MediumHandler2 : public MediumA,
+ public FRT_Invokable,
+ public MediumB
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+
+class MediumHandler3 : public MediumA,
+ public MediumB,
+ public FRT_Invokable
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+//-------------------------------------------------------------
+
+class ComplexHandler1 : public FRT_Invokable,
+ public ComplexA,
+ public ComplexB
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+
+class ComplexHandler2 : public ComplexA,
+ public FRT_Invokable,
+ public ComplexB
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+
+class ComplexHandler3 : public ComplexA,
+ public ComplexB,
+ public FRT_Invokable
+{
+public:
+ virtual void foo() {}
+ virtual void bar() {}
+ void RPC_Method(FRT_RPCRequest *req);
+};
+
+//-------------------------------------------------------------
+
+void initTest() {
+ _supervisor = new FRT_Supervisor();
+ _simpleHandler = new SimpleHandler();
+ _mediumHandler1 = new MediumHandler1();
+ _mediumHandler2 = new MediumHandler2();
+ _mediumHandler3 = new MediumHandler3();
+ _complexHandler1 = new ComplexHandler1();
+ _complexHandler2 = new ComplexHandler2();
+ _complexHandler3 = new ComplexHandler3();
+
+ ASSERT_TRUE(_supervisor != NULL);
+ ASSERT_TRUE(_simpleHandler != NULL);
+ ASSERT_TRUE(_mediumHandler1 != NULL);
+ ASSERT_TRUE(_mediumHandler2 != NULL);
+ ASSERT_TRUE(_mediumHandler3 != NULL);
+ ASSERT_TRUE(_complexHandler1 != NULL);
+ ASSERT_TRUE(_complexHandler2 != NULL);
+ ASSERT_TRUE(_complexHandler3 != NULL);
+
+ ASSERT_TRUE(_supervisor->Listen(0));
+ std::string spec = vespalib::make_string("tcp/localhost:%d",
+ _supervisor->GetListenPort());
+ _target = _supervisor->GetTarget(spec.c_str());
+ ASSERT_TRUE(_target != NULL);
+
+ bool startOK = _supervisor->Start();
+ ASSERT_TRUE(startOK);
+
+ FRT_ReflectionBuilder rb(_supervisor);
+
+ //-------------------------------------------------------------------
+
+ rb.DefineMethod("simpleMethod", "", "", true,
+ FRT_METHOD(SimpleHandler::RPC_Method),
+ _simpleHandler);
+
+ //-------------------------------------------------------------------
+
+ rb.DefineMethod("mediumMethod1", "", "", true,
+ FRT_METHOD(MediumHandler1::RPC_Method),
+ _mediumHandler1);
+
+ rb.DefineMethod("mediumMethod2", "", "", true,
+ FRT_METHOD(MediumHandler2::RPC_Method),
+ _mediumHandler2);
+
+ rb.DefineMethod("mediumMethod3", "", "", true,
+ FRT_METHOD(MediumHandler3::RPC_Method),
+ _mediumHandler3);
+
+ //-------------------------------------------------------------------
+
+ rb.DefineMethod("complexMethod1", "", "", true,
+ FRT_METHOD(ComplexHandler1::RPC_Method),
+ _complexHandler1);
+
+ rb.DefineMethod("complexMethod2", "", "", true,
+ FRT_METHOD(ComplexHandler2::RPC_Method),
+ _complexHandler2);
+
+ rb.DefineMethod("complexMethod3", "", "", true,
+ FRT_METHOD(ComplexHandler3::RPC_Method),
+ _complexHandler3);
+
+ //-------------------------------------------------------------------
+
+ _mediumHandlerOK = true;
+ _complexHandlerOK = true;
+}
+
+
+void finiTest() {
+ _supervisor->ShutDown(true);
+ delete _complexHandler1;
+ delete _complexHandler2;
+ delete _complexHandler3;
+ delete _mediumHandler1;
+ delete _mediumHandler2;
+ delete _mediumHandler3;
+ delete _simpleHandler;
+ _target->SubRef();
+ delete _supervisor;
+}
+
+
+TEST("method pt") {
+ FRT_RPCRequest *req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("simpleMethod");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ //-------------------------------- MEDIUM
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("mediumMethod1");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("mediumMethod2");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("mediumMethod3");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ //-------------------------------- COMPLEX
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("complexMethod1");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("complexMethod2");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ req->SubRef();
+ req = _supervisor->AllocRPCRequest();
+ req->SetMethodName("complexMethod3");
+ _target->InvokeSync(req, 60.0);
+ EXPECT_TRUE(!req->IsError());
+
+ if (_mediumHandlerOK) {
+ fprintf(stderr, "Interface inheritance OK for method handlers\n");
+ } else {
+ fprintf(stderr, "Interface inheritance NOT ok for method handlers\n");
+ }
+
+ if (_complexHandlerOK) {
+ fprintf(stderr, "Object inheritance OK for method handlers\n");
+ } else {
+ fprintf(stderr, "Object inheritance NOT ok for method handlers\n");
+ }
+
+ req->SubRef();
+}
+
+//-------------------------------------------------------------
+
+void
+SimpleHandler::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ EXPECT_TRUE(this == _simpleHandler);
+}
+
+//-------------------------------------------------------------
+
+void
+MediumHandler1::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _mediumHandlerOK = (_mediumHandlerOK &&
+ this == _mediumHandler1);
+}
+
+
+void
+MediumHandler2::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _mediumHandlerOK = (_mediumHandlerOK &&
+ this == _mediumHandler2);
+}
+
+
+void
+MediumHandler3::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _mediumHandlerOK = (_mediumHandlerOK &&
+ this == _mediumHandler3);
+}
+
+//-------------------------------------------------------------
+
+void
+ComplexHandler1::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _complexHandlerOK = (_complexHandlerOK &&
+ this == _complexHandler1);
+}
+
+
+void
+ComplexHandler2::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _complexHandlerOK = (_complexHandlerOK &&
+ this == _complexHandler2);
+}
+
+
+void
+ComplexHandler3::RPC_Method(FRT_RPCRequest *req)
+{
+ (void) req;
+ _complexHandlerOK = (_complexHandlerOK &&
+ this == _complexHandler3);
+}
+
+//-------------------------------------------------------------
+
+TEST_MAIN() {
+ initTest();
+ TEST_RUN_ALL();
+ finiTest();
+}
diff --git a/fnet/src/tests/frt/parallel_rpc/.gitignore b/fnet/src/tests/frt/parallel_rpc/.gitignore
new file mode 100644
index 00000000000..7b4b7428e52
--- /dev/null
+++ b/fnet/src/tests/frt/parallel_rpc/.gitignore
@@ -0,0 +1 @@
+fnet_parallel_rpc_test_app
diff --git a/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt b/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt
new file mode 100644
index 00000000000..00a0c12e413
--- /dev/null
+++ b/fnet/src/tests/frt/parallel_rpc/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_parallel_rpc_test_app
+ SOURCES
+ parallel_rpc_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_parallel_rpc_test_app COMMAND fnet_parallel_rpc_test_app)
diff --git a/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp b/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp
new file mode 100644
index 00000000000..723f519cd37
--- /dev/null
+++ b/fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp
@@ -0,0 +1,129 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/vespalib/util/benchmark_timer.h>
+#include <thread>
+
+using vespalib::BenchmarkTimer;
+
+struct Rpc : FRT_Invokable {
+ FastOS_ThreadPool thread_pool;
+ FNET_Transport transport;
+ FRT_Supervisor orb;
+ Rpc(size_t num_threads)
+ : thread_pool(128 * 1024), transport(num_threads), orb(&transport, &thread_pool) {}
+ void start() {
+ ASSERT_TRUE(transport.Start(&thread_pool));
+ }
+ uint32_t listen() {
+ ASSERT_TRUE(orb.Listen(0));
+ return orb.GetListenPort();
+ }
+ FRT_Target *connect(uint32_t port) {
+ return orb.GetTarget(port);
+ }
+ ~Rpc() {
+ transport.ShutDown(true);
+ thread_pool.Close();
+ }
+};
+
+struct Server : Rpc {
+ uint32_t port;
+ Server(size_t num_threads) : Rpc(num_threads), port(listen()) {
+ init_rpc();
+ start();
+ }
+ void init_rpc() {
+ FRT_ReflectionBuilder rb(&orb);
+ rb.DefineMethod("inc", "l", "l", true, FRT_METHOD(Server::rpc_inc), this);
+ rb.MethodDesc("increment a 64-bit integer");
+ rb.ParamDesc("in", "an integer (64 bit)");
+ rb.ReturnDesc("out", "in + 1 (64 bit)");
+ }
+ void rpc_inc(FRT_RPCRequest *req) {
+ FRT_Values &params = *req->GetParams();
+ FRT_Values &ret = *req->GetReturn();
+ ret.AddInt64(params[0]._intval64 + 1);
+ }
+};
+
+struct Client : Rpc {
+ uint32_t port;
+ Client(size_t num_threads, const Server &server) : Rpc(num_threads), port(server.port) {
+ start();
+ }
+ FRT_Target *connect() { return Rpc::connect(port); }
+};
+
+struct Result {
+ std::vector<double> req_per_sec;
+ Result(size_t num_threads) : req_per_sec(num_threads, 0.0) {}
+ double throughput() const {
+ double sum = 0.0;
+ for (double sample: req_per_sec) {
+ sum += sample;
+ }
+ return sum;
+ }
+ double latency_ms() const {
+ double avg_req_per_sec = throughput() / req_per_sec.size();
+ double avg_sec_per_req = 1.0 / avg_req_per_sec;
+ return avg_sec_per_req * 1000.0;
+ }
+ void print() const {
+ fprintf(stderr, "total throughput: %f req/s\n", throughput());
+ fprintf(stderr, "average latency : %f ms\n", latency_ms());
+ }
+};
+
+void perform_test(size_t thread_id, Client &client, Result &result) {
+ uint64_t seq = 0;
+ FRT_Target *target = client.connect();
+ FRT_RPCRequest *req = client.orb.AllocRPCRequest();
+ auto invoke = [&seq, target, &client, &req](){
+ req = client.orb.AllocRPCRequest(req);
+ req->SetMethodName("inc");
+ req->GetParams()->AddInt64(seq);
+ target->InvokeSync(req, 60.0);
+ ASSERT_TRUE(req->CheckReturnTypes("l"));
+ uint64_t ret = req->GetReturn()->GetValue(0)._intval64;
+ EXPECT_EQUAL(ret, seq + 1);
+ seq = ret;
+ };
+ size_t loop_cnt = 128;
+ BenchmarkTimer::benchmark(invoke, invoke, 1.0);
+ BenchmarkTimer timer(3.0);
+ while (timer.has_budget()) {
+ timer.before();
+ for (size_t i = 0; i < loop_cnt; ++i) {
+ invoke();
+ }
+ timer.after();
+ }
+ double t = timer.min_time();
+ BenchmarkTimer::benchmark(invoke, invoke, 1.0);
+ EXPECT_GREATER_EQUAL(seq, loop_cnt);
+ result.req_per_sec[thread_id] = double(loop_cnt) / t;
+ req->SubRef();
+ target->SubRef();
+ TEST_BARRIER();
+ if (thread_id == 0) {
+ result.print();
+ }
+}
+
+TEST_MT_FFF("parallel rpc with 1/1 transport threads and 128 user threads",
+ 128, Server(1), Client(1, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); }
+
+TEST_MT_FFF("parallel rpc with 1/8 transport threads and 128 user threads",
+ 128, Server(8), Client(1, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); }
+
+TEST_MT_FFF("parallel rpc with 8/1 transport threads and 128 user threads",
+ 128, Server(1), Client(8, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); }
+
+TEST_MT_FFF("parallel rpc with 8/8 transport threads and 128 user threads",
+ 128, Server(8), Client(8, f1), Result(num_threads)) { perform_test(thread_id, f2, f3); }
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/rpc/.gitignore b/fnet/src/tests/frt/rpc/.gitignore
new file mode 100644
index 00000000000..be31ed66868
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/.gitignore
@@ -0,0 +1,12 @@
+*.core
+.depend
+Makefile
+core
+detach_return_invoke_test
+invoke_test
+session_test
+sharedblob_test
+fnet_detach_return_invoke_test_app
+fnet_invoke_test_app
+fnet_session_test_app
+fnet_sharedblob_test_app
diff --git a/fnet/src/tests/frt/rpc/CMakeLists.txt b/fnet/src/tests/frt/rpc/CMakeLists.txt
new file mode 100644
index 00000000000..806a78ec6b7
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_invoke_test_app
+ SOURCES
+ invoke.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_invoke_test_app COMMAND fnet_invoke_test_app)
+vespa_add_executable(fnet_detach_return_invoke_test_app
+ SOURCES
+ detach_return_invoke.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_detach_return_invoke_test_app COMMAND fnet_detach_return_invoke_test_app)
+vespa_add_executable(fnet_session_test_app
+ SOURCES
+ session.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_session_test_app COMMAND fnet_session_test_app)
+vespa_add_executable(fnet_sharedblob_test_app
+ SOURCES
+ sharedblob.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_sharedblob_test_app COMMAND fnet_sharedblob_test_app)
diff --git a/fnet/src/tests/frt/rpc/DESC b/fnet/src/tests/frt/rpc/DESC
new file mode 100644
index 00000000000..017c68b41f8
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/DESC
@@ -0,0 +1 @@
+Various tests related to rpc invocation.
diff --git a/fnet/src/tests/frt/rpc/FILES b/fnet/src/tests/frt/rpc/FILES
new file mode 100644
index 00000000000..e038169da5d
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/FILES
@@ -0,0 +1,3 @@
+invoke.cpp
+session.cpp
+sharedblob.cpp
diff --git a/fnet/src/tests/frt/rpc/detach_return_invoke.cpp b/fnet/src/tests/frt/rpc/detach_return_invoke.cpp
new file mode 100644
index 00000000000..b689671372b
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/detach_return_invoke.cpp
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+struct Receptor : public FRT_IRequestWait
+{
+ FRT_RPCRequest *req;
+
+ Receptor() : req(0) {}
+ void RequestDone(FRT_RPCRequest *r) {
+ req = r;
+ }
+};
+
+struct Server : public FRT_Invokable
+{
+ FRT_Supervisor &orb;
+ Receptor &receptor;
+
+ Server(FRT_Supervisor &s, Receptor &r) : orb(s), receptor(r) {
+ FRT_ReflectionBuilder rb(&s);
+ rb.DefineMethod("hook", "", "", true,
+ FRT_METHOD(Server::rpc_hook), this);
+ }
+
+ void rpc_hook(FRT_RPCRequest *req) {
+ FNET_Connection *conn = req->GetConnection();
+ conn->AddRef(); // need to keep it alive
+ req->Detach();
+ req->Return(); // will free request channel
+ FRT_RPCRequest *r = orb.AllocRPCRequest();
+ r->SetMethodName("frt.rpc.ping");
+ // might re-use request channel before it is unlinked from hashmap
+ orb.InvokeAsync(orb.GetTransport(), conn, r, 5.0, &receptor);
+ conn->SubRef(); // invocation will now keep the connection alive as needed
+ }
+};
+
+TEST("detach return invoke") {
+ Receptor receptor;
+ FRT_Supervisor orb;
+ Server server(orb, receptor);
+ ASSERT_TRUE(orb.Listen(0));
+ ASSERT_TRUE(orb.Start());
+ std::string spec = vespalib::make_string("tcp/localhost:%d", orb.GetListenPort());
+ FRT_Target *target = orb.Get2WayTarget(spec.c_str());
+ FRT_RPCRequest *req = orb.AllocRPCRequest();
+
+ req->SetMethodName("hook");
+ target->InvokeSync(req, 5.0);
+ EXPECT_TRUE(!req->IsError());
+ for (uint32_t i = 0; i < 1000; ++i) {
+ if (receptor.req != 0) {
+ break;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ req->SubRef();
+ target->SubRef();
+ orb.ShutDown(true);
+ if (receptor.req != 0) {
+ EXPECT_TRUE(!receptor.req->IsError());
+ receptor.req->SubRef();
+ }
+ EXPECT_TRUE(receptor.req != 0);
+};
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/rpc/invoke.cpp b/fnet/src/tests/frt/rpc/invoke.cpp
new file mode 100644
index 00000000000..7983d2eb9d8
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/invoke.cpp
@@ -0,0 +1,938 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+
+//-------------------------------------------------------------
+
+FNET_Mutex _delayedReturnCntLock;
+uint32_t _delayedReturnCnt = 0;
+
+uint32_t _phase_simple_cnt = 0;
+uint32_t _phase_void_cnt = 0;
+uint32_t _phase_speed_cnt = 0;
+uint32_t _phase_advanced_cnt = 0;
+uint32_t _phase_error_cnt = 0;
+uint32_t _phase_timeout_cnt = 0;
+uint32_t _phase_abort_cnt = 0;
+uint32_t _phase_echo_cnt = 0;
+
+//-------------------------------------------------------------
+
+struct LockedReqWait : public FRT_IRequestWait
+{
+ FNET_Cond _cond; // cond used to signal req done
+ bool _done; // flag indicating req done
+
+ FNET_Mutex _lockLock; // lock protecting virtual lock
+ bool _lock; // virtual lock
+ bool _wasLocked; // was 'locked' when req done
+
+ LockedReqWait() : _cond(), _done(false), _lockLock(), _lock(false), _wasLocked(false) {}
+
+ void lock() {
+ _lockLock.Lock();
+ _lock = true;
+ _lockLock.Unlock();
+ }
+
+ void unlock() {
+ _lockLock.Lock();
+ _lock = false;
+ _lockLock.Unlock();
+ }
+
+ bool isLocked() {
+ _lockLock.Lock();
+ bool ret = _lock;
+ _lockLock.Unlock();
+ return ret;
+ }
+
+ virtual void RequestDone(FRT_RPCRequest *)
+ {
+ _wasLocked = isLocked();
+ _cond.Lock();
+ _done = true;
+ _cond.Signal();
+ _cond.Unlock();
+ }
+
+ void waitReq()
+ {
+ _cond.Lock();
+ while(!_done) {
+ _cond.Wait();
+ }
+ _cond.Unlock();
+ }
+};
+
+//-------------------------------------------------------------
+
+class DelayedReturn : public FNET_Task
+{
+private:
+ FRT_RPCRequest *_req;
+
+ DelayedReturn(const DelayedReturn &);
+ DelayedReturn &operator=(const DelayedReturn &);
+
+public:
+ DelayedReturn(FNET_Scheduler *sched,
+ FRT_RPCRequest *req,
+ double delay)
+ : FNET_Task(sched),
+ _req(req)
+ {
+ _delayedReturnCntLock.Lock();
+ _delayedReturnCnt++;
+ _delayedReturnCntLock.Unlock();
+ Schedule(delay);
+ }
+
+ void PerformTask()
+ {
+ _req->Return();
+ _delayedReturnCntLock.Lock();
+ _delayedReturnCnt--;
+ _delayedReturnCntLock.Unlock();
+ }
+};
+
+//-------------------------------------------------------------
+
+class EchoTest : public FRT_Invokable
+{
+private:
+ FRT_MemoryTub *_echo_tub;
+ FRT_Values *_echo_args;
+
+ EchoTest(const EchoTest &);
+ EchoTest &operator=(const EchoTest &);
+
+public:
+ EchoTest() : _echo_tub(NULL), _echo_args(NULL) {}
+ ~EchoTest()
+ {
+ delete _echo_args;
+ delete _echo_tub;
+ }
+
+ void Init(FRT_Supervisor *supervisor)
+ {
+ _echo_tub = new FRT_MemoryTub();
+ _echo_args = new FRT_Values(_echo_tub);
+ assert(_echo_tub != NULL && _echo_args != NULL);
+
+ FRT_ReflectionBuilder rb(supervisor);
+ rb.DefineMethod("echo", "*", "*", true,
+ FRT_METHOD(EchoTest::RPC_Echo), this);
+
+ FRT_Values *args = _echo_args;
+ args->EnsureFree(16);
+
+ args->AddInt8(8);
+ uint8_t *pt_int8 = args->AddInt8Array(3);
+ pt_int8[0] = 1;
+ pt_int8[1] = 2;
+ pt_int8[2] = 3;
+
+ args->AddInt16(16);
+ uint16_t *pt_int16 = args->AddInt16Array(3);
+ pt_int16[0] = 2;
+ pt_int16[1] = 4;
+ pt_int16[2] = 6;
+
+ args->AddInt32(32);
+ uint32_t *pt_int32 = args->AddInt32Array(3);
+ pt_int32[0] = 4;
+ pt_int32[1] = 8;
+ pt_int32[2] = 12;
+
+ args->AddInt64(64);
+ uint64_t *pt_int64 = args->AddInt64Array(3);
+ pt_int64[0] = 8;
+ pt_int64[1] = 16;
+ pt_int64[2] = 24;
+
+ args->AddFloat(32.5);
+ float *pt_float = args->AddFloatArray(3);
+ pt_float[0] = 0.25;
+ pt_float[1] = 0.5;
+ pt_float[2] = 0.75;
+
+ args->AddDouble(64.5);
+ double *pt_double = args->AddDoubleArray(3);
+ pt_double[0] = 0.1;
+ pt_double[1] = 0.2;
+ pt_double[2] = 0.3;
+
+ args->AddString("string");
+ FRT_StringValue *pt_string = args->AddStringArray(3);
+ args->SetString(&pt_string[0], "str1");
+ args->SetString(&pt_string[1], "str2");
+ args->SetString(&pt_string[2], "str3");
+
+ args->AddData("data", 4);
+ FRT_DataValue *pt_data = args->AddDataArray(3);
+ args->SetData(&pt_data[0], "dat1", 4);
+ args->SetData(&pt_data[1], "dat2", 4);
+ args->SetData(&pt_data[2], "dat3", 4);
+ }
+
+ bool PrepareEchoReq(FRT_RPCRequest *req)
+ {
+ FNET_DataBuffer buf;
+
+ req->SetMethodName("echo");
+ _echo_args->EncodeCopy(&buf);
+ req->GetParams()->DecodeCopy(&buf, buf.GetDataLen());
+ return (req->GetParams()->Equals(_echo_args) &&
+ _echo_args->Equals(req->GetParams()));
+ }
+
+ void RPC_Echo(FRT_RPCRequest *req)
+ {
+ FNET_DataBuffer buf;
+
+ req->GetParams()->EncodeCopy(&buf);
+ req->GetReturn()->DecodeCopy(&buf, buf.GetDataLen());
+ if (!req->GetReturn()->Equals(_echo_args) ||
+ !req->GetReturn()->Equals(req->GetParams()))
+ {
+ req->SetError(10000, "Streaming error");
+ }
+ }
+};
+
+//-------------------------------------------------------------
+
+class TestRPC : public FRT_Invokable
+{
+private:
+ FRT_Supervisor *_supervisor;
+ FNET_Scheduler *_scheduler;
+ uint32_t _intValue;
+
+ TestRPC(const TestRPC &);
+ TestRPC &operator=(const TestRPC &);
+
+public:
+ TestRPC(FRT_Supervisor *supervisor, // server supervisor
+ FNET_Scheduler *scheduler) // client scheduler
+ : _supervisor(supervisor),
+ _scheduler(scheduler),
+ _intValue(0)
+ {
+ FRT_ReflectionBuilder rb(supervisor);
+
+ rb.DefineMethod("inc", "i", "i", true,
+ FRT_METHOD(TestRPC::RPC_Inc), this);
+ rb.DefineMethod("setValue", "i", "", true,
+ FRT_METHOD(TestRPC::RPC_SetValue), this);
+ rb.DefineMethod("incValue", "", "", true,
+ FRT_METHOD(TestRPC::RPC_IncValue), this);
+ rb.DefineMethod("getValue", "", "i", true,
+ FRT_METHOD(TestRPC::RPC_GetValue), this);
+ rb.DefineMethod("testFast", "iiibb", "i", true,
+ FRT_METHOD(TestRPC::RPC_Test), this);
+ rb.DefineMethod("testSlow", "iiibb", "i", false,
+ FRT_METHOD(TestRPC::RPC_Test), this);
+ }
+
+ void RPC_Test(FRT_RPCRequest *req)
+ {
+ FRT_Values &param = *req->GetParams();
+ uint32_t value = param[0]._intval32;
+ uint32_t delay = param[1]._intval32;
+ uint32_t error = param[2]._intval32;
+ uint8_t extra = param[3]._intval8;
+ uint8_t async = param[4]._intval8;
+
+ req->GetReturn()->AddInt32(value);
+ if (extra != 0) {
+ req->GetReturn()->AddInt32(value);
+ }
+ if (error != 0) {
+ req->SetError(error);
+ }
+ if (async != 0) {
+ req->Detach();
+ if (delay == 0) {
+ req->Return();
+ } else {
+ new (req->GetMemoryTub()) DelayedReturn(_scheduler,
+ req,
+ ((double)delay) / 1000.0);
+ }
+ } else {
+
+ if (delay > 0) {
+
+ const char *suffix = "testFast";
+ uint32_t suffix_len = strlen(suffix);
+ uint32_t name_len = req->GetMethodNameLen();
+ bool remote = req->GetContext()._value.VOIDP != NULL;
+ bool instant = name_len > suffix_len &&
+ strcmp(req->GetMethodName() + name_len - suffix_len, suffix) == 0;
+
+ if (remote && instant) {
+
+ // block, but don't cripple server scheduler...
+ // (NB: in 'real life', instant methods should never block)
+
+ FastOS_Time *now = _supervisor->GetTransport()->GetTimeSampler();
+ FNET_Scheduler *scheduler = _supervisor->GetScheduler();
+ assert(scheduler->GetTimeSampler() == now);
+
+ while (delay > 0) {
+ if (delay > 20) {
+ FastOS_Thread::Sleep(20);
+ delay -= 20;
+ } else {
+ FastOS_Thread::Sleep(delay);
+ delay = 0;
+ }
+ now->SetNow();
+ scheduler->CheckTasks();
+ }
+
+ } else {
+
+ FastOS_Thread::Sleep(delay);
+ }
+ }
+ }
+ }
+
+ void RPC_Inc(FRT_RPCRequest *req)
+ {
+ req->GetReturn()->AddInt32(req->GetParams()->GetValue(0)._intval32 + 1);
+ }
+
+ void RPC_SetValue(FRT_RPCRequest *req)
+ {
+ _intValue = req->GetParams()->GetValue(0)._intval32;
+ }
+
+ void RPC_IncValue(FRT_RPCRequest *req)
+ {
+ (void) req;
+ _intValue++;
+ }
+
+ void RPC_GetValue(FRT_RPCRequest *req)
+ {
+ req->GetReturn()->AddInt32(_intValue);
+ }
+};
+
+//-------------------------------------------------------------
+
+enum {
+ OK_RET = 0,
+ BOGUS_RET = 1
+};
+
+enum {
+ PHASE_NULL = 0,
+ PHASE_SETUP,
+ PHASE_SIMPLE,
+ PHASE_VOID,
+ PHASE_SPEED,
+ PHASE_ADVANCED,
+ PHASE_ERROR,
+ PHASE_TIMEOUT,
+ PHASE_ABORT,
+ PHASE_ECHO,
+ PHASE_SHUTDOWN,
+ PHASE_ZZZ
+};
+
+const char phase_names[PHASE_ZZZ][32] =
+{
+ "NULL",
+ "SETUP",
+ "SIMPLE",
+ "VOID",
+ "SPEED",
+ "ADVANCED",
+ "ERROR",
+ "TIMEOUT",
+ "ABORT",
+ "ECHO",
+ "SHUTDOWN"
+};
+
+enum {
+ TIMING_NULL = 0,
+ TIMING_INSTANT,
+ TIMING_NON_INSTANT,
+ TIMING_ZZZ
+};
+
+const char timing_names[TIMING_ZZZ][32] =
+{
+ "NULL",
+ "INSTANT",
+ "NON-INSTANT"
+};
+
+enum {
+ HANDLING_NULL = 0,
+ HANDLING_SYNC,
+ HANDLING_ASYNC,
+ HANDLING_ZZZ
+};
+
+const char handling_names[HANDLING_ZZZ][32] =
+{
+ "NULL",
+ "SYNC",
+ "ASYNC"
+};
+
+//-------------------------------------------------------------
+
+struct State {
+ FRT_Supervisor _client;
+ FRT_Supervisor _server;
+ TestRPC _rpc;
+ EchoTest _echo;
+ std::string _peerSpec;
+ uint32_t _testPhase;
+ uint32_t _timing;
+ uint32_t _handling;
+ double _timeout;
+ FRT_Target *_target;
+ FRT_RPCRequest *_req;
+
+ State()
+ : _client(),
+ _server(),
+ _rpc(&_server, _client.GetScheduler()),
+ _echo(),
+ _peerSpec(),
+ _testPhase(PHASE_NULL),
+ _timing(TIMING_NULL),
+ _handling(HANDLING_NULL),
+ _timeout(5.0),
+ _target(NULL),
+ _req(NULL)
+ {
+ _client.GetTransport()->SetTCPNoDelay(true);
+ _server.GetTransport()->SetTCPNoDelay(true);
+ _echo.Init(&_server);
+ }
+
+ void SetTimeout(double timeout)
+ {
+ _timeout = timeout;
+ }
+
+ void NewReq()
+ {
+ if (_req != NULL) {
+ _req->SubRef();
+ }
+ _req = new FRT_RPCRequest();
+ }
+
+ void FreeReq()
+ {
+ if (_req != NULL) {
+ _req->SubRef();
+ }
+ _req = NULL;
+ }
+
+ void LostReq()
+ {
+ _req = NULL;
+ }
+
+ void PrepareTestMethod()
+ {
+ NewReq();
+ bool instant = (_timing == TIMING_INSTANT);
+ if (_timing != TIMING_INSTANT &&
+ _timing != TIMING_NON_INSTANT)
+ {
+ ASSERT_TRUE(false); // consult your dealer...
+ }
+ if (instant) {
+ _req->SetMethodName("testFast");
+ } else {
+ _req->SetMethodName("testSlow");
+ }
+ }
+
+ void SetTestParams(uint32_t value, uint32_t delay,
+ uint32_t error = FRTE_NO_ERROR,
+ uint8_t extra = 0)
+ {
+ _req->GetParams()->AddInt32(value);
+ _req->GetParams()->AddInt32(delay);
+ _req->GetParams()->AddInt32(error);
+ _req->GetParams()->AddInt8(extra);
+ bool async = (_handling == HANDLING_ASYNC);
+ if (_handling != HANDLING_SYNC &&
+ _handling != HANDLING_ASYNC)
+ {
+ ASSERT_TRUE(false); // consult your dealer...
+ }
+ _req->GetParams()->AddInt8((async) ? 1 : 0);
+ }
+
+ void InvokeSync();
+ void InvokeVoid();
+ void InvokeAsync(FRT_IRequestWait *w);
+ void InvokeTest(uint32_t value,
+ uint32_t delay = 0,
+ uint32_t error = FRTE_NO_ERROR,
+ uint8_t extra = 0);
+ void InvokeTestAndAbort(uint32_t value,
+ uint32_t delay = 0,
+ uint32_t error = FRTE_NO_ERROR,
+ uint8_t extra = 0);
+ bool WaitForDelayedReturnCount(uint32_t wantedCount, double timeout);
+
+private:
+ State(const State &);
+ State &operator=(const State &);
+};
+
+
+void
+State::InvokeSync()
+{
+ _target->InvokeSync(_req, _timeout);
+}
+
+
+void
+State::InvokeVoid()
+{
+ _target->InvokeVoid(_req);
+}
+
+
+void
+State::InvokeAsync(FRT_IRequestWait *w)
+{
+ _target->InvokeAsync(_req, _timeout, w);
+}
+
+
+void
+State::InvokeTest(uint32_t value, uint32_t delay,
+ uint32_t error, uint8_t extra)
+{
+ PrepareTestMethod();
+ SetTestParams(value, delay, error, extra);
+ InvokeSync();
+}
+
+
+void
+State::InvokeTestAndAbort(uint32_t value, uint32_t delay,
+ uint32_t error, uint8_t extra)
+{
+ PrepareTestMethod();
+ SetTestParams(value, delay, error, extra);
+ FRT_SingleReqWait w;
+ InvokeAsync(&w);
+ _req->Abort();
+ w.WaitReq();
+}
+
+bool
+State::WaitForDelayedReturnCount(uint32_t wantedCount, double timeout)
+{
+ FastOS_Time timer;
+ timer.SetNow();
+ for (;;) {
+ _delayedReturnCntLock.Lock();
+ uint32_t delayedReturnCnt = _delayedReturnCnt;
+ _delayedReturnCntLock.Unlock();
+ if (delayedReturnCnt == wantedCount) {
+ return true;
+ }
+ if ((timer.MilliSecsToNow() / 1000.0) > timeout) {
+ return false;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+}
+
+//-------------------------------------------------------------
+
+bool CheckTypes(FRT_RPCRequest *req, const char *spec) {
+ return FRT_Values::CheckTypes(spec, req->GetReturnSpec());
+}
+
+FRT_Value &Get(FRT_RPCRequest *req, uint32_t idx) {
+ return req->GetReturn()->GetValue(idx);
+}
+
+//-------------------------------------------------------------
+
+void TestSetup(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_SETUP);
+
+ bool listenOK = _state->_server.Listen("tcp/0");
+
+ char spec[64];
+ sprintf(spec, "tcp/localhost:%d", _state->_server.GetListenPort());
+ _state->_peerSpec = spec;
+
+ bool serverStartOK = _state->_server.Start();
+ bool clientStartOK = _state->_client.Start();
+
+ ASSERT_TRUE(listenOK);
+ ASSERT_TRUE(serverStartOK);
+ ASSERT_TRUE(clientStartOK);
+
+ _state->_target = _state->_client.GetTarget(_state->_peerSpec.c_str());
+ _state->NewReq();
+ _state->_req->SetMethodName("frt.rpc.ping");
+ _state->_target->InvokeSync(_state->_req, 5.0);
+ ASSERT_TRUE(!_state->_req->IsError());
+}
+
+
+void TestSimple(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_SIMPLE);
+ _phase_simple_cnt++;
+ _state->NewReq();
+ _state->_req->SetMethodName("inc");
+ _state->_req->GetParams()->AddInt32(502);
+ _state->InvokeSync();
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, "i") &&
+ Get(_state->_req, 0)._intval32 == 503);
+}
+
+
+void TestVoid(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_VOID);
+ _phase_void_cnt++;
+
+ _state->NewReq();
+ _state->_req->SetMethodName("setValue");
+ _state->_req->GetParams()->AddInt32(40);
+ _state->InvokeSync();
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, ""));
+
+ _state->NewReq();
+ _state->_req->SetMethodName("incValue");
+ _state->InvokeVoid();
+ _state->LostReq();
+
+ _state->NewReq();
+ _state->_req->SetMethodName("incValue");
+ _state->InvokeVoid();
+ _state->LostReq();
+
+ _state->NewReq();
+ _state->_req->SetMethodName("getValue");
+ _state->InvokeSync();
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, "i") &&
+ Get(_state->_req, 0)._intval32 == 42);
+}
+
+
+void TestSpeed(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_SPEED);
+ _phase_speed_cnt++;
+
+ FastOS_Time start;
+ FastOS_Time stop;
+ uint32_t val = 0;
+ uint32_t cnt = 0;
+
+ _state->NewReq();
+ FRT_RPCRequest *req = _state->_req;
+ FRT_Target *target = _state->_target;
+
+ // calibrate cnt to be used
+ start.SetNow();
+ for (cnt = 0; cnt < 1000000; cnt++) {
+ req->SetMethodName("inc");
+ req->GetParams()->AddInt32(0);
+ target->InvokeSync(req, 5.0);
+ if (req->IsError()) {
+ break;
+ }
+ req->Reset(); // ok if no error
+ if (start.MilliSecsToNow() > 20.0) {
+ break;
+ }
+ }
+ cnt = (cnt == 0) ? 1 : cnt * 10;
+
+ fprintf(stderr, "checking invocation latency... (cnt = %d)\n", cnt);
+
+ _state->NewReq();
+ req = _state->_req;
+
+ // actual benchmark
+ start.SetNow();
+ for (uint32_t i = 0; i < cnt; i++) {
+ req->SetMethodName("inc");
+ req->GetParams()->AddInt32(val);
+ target->InvokeSync(req, 60.0);
+ if (req->IsError()) {
+ fprintf(stderr, "... rpc error(%d): %s\n",
+ req->GetErrorCode(),
+ req->GetErrorMessage());
+ break;
+ }
+ val = req->GetReturn()->GetValue(0)._intval32;
+ req->Reset(); // ok if no error
+ }
+ stop.SetNow();
+ stop -= start;
+ double latency = stop.MilliSecs() / (double) cnt;
+
+ EXPECT_EQUAL(val, cnt);
+ fprintf(stderr, "latency of invocation: %1.3f ms\n", latency);
+}
+
+
+void TestAdvanced(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_ADVANCED);
+ _phase_advanced_cnt++;
+
+ // Test invocation
+ //----------------
+ _state->InvokeTest(42);
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, "i") &&
+ Get(_state->_req, 0)._intval32 == 42);
+
+ // Abort has no effect after request is done
+ //------------------------------------------
+ _state->_req->Abort();
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, "i") &&
+ Get(_state->_req, 0)._intval32 == 42);
+
+ // Test invocation with delay
+ //---------------------------
+ _state->InvokeTest(58, 100);
+ EXPECT_TRUE(!_state->_req->IsError() &&
+ CheckTypes(_state->_req, "i") &&
+ Get(_state->_req, 0)._intval32 == 58);
+}
+
+
+void TestError(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_ERROR);
+ _phase_error_cnt++;
+
+ // bad target -> sync error -> avoid deadlock
+ //-------------------------------------------
+ if (_state->_handling == HANDLING_ASYNC)
+ {
+ // stash away valid target
+ FRT_Target *stateTarget = _state->_target; // backup of valid target
+
+ _state->_target = _state->_client.GetTarget("bogus address");
+ _state->NewReq();
+ _state->_req->SetMethodName("frt.rpc.ping");
+ LockedReqWait lw;
+ lw.lock();
+ _state->InvokeAsync(&lw);
+ lw.unlock();
+ lw.waitReq();
+ EXPECT_TRUE(!lw._wasLocked);
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_CONNECTION);
+
+ // restore valid target
+ _state->_target->SubRef();
+ _state->_target = stateTarget;
+ }
+
+ // no such method
+ //---------------
+ if (_state->_timing == TIMING_INSTANT &&
+ _state->_handling == HANDLING_SYNC)
+ {
+ _state->NewReq();
+ _state->_req->SetMethodName("bogus");
+ _state->InvokeSync();
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_NO_SUCH_METHOD);
+ }
+
+ // wrong params
+ //-------------
+ if (_state->_handling == HANDLING_SYNC) {
+
+ _state->PrepareTestMethod();
+ _state->InvokeSync();
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS);
+
+ _state->PrepareTestMethod();
+ _state->_req->GetParams()->AddInt32(42);
+ _state->_req->GetParams()->AddInt32(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->InvokeSync();
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS);
+
+ _state->PrepareTestMethod();
+ _state->_req->GetParams()->AddInt32(42);
+ _state->_req->GetParams()->AddInt32(0);
+ _state->_req->GetParams()->AddInt32(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->_req->GetParams()->AddInt8(0);
+ _state->InvokeSync();
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_PARAMS);
+ }
+
+ // wrong return
+ //-------------
+ _state->InvokeTest(42, 0, 0, BOGUS_RET);
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_WRONG_RETURN);
+
+ // method failed
+ //--------------
+ _state->InvokeTest(42, 0, 5000, BOGUS_RET);
+ EXPECT_TRUE(_state->_req->GetErrorCode() == 5000);
+}
+
+
+void TestTimeout(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_TIMEOUT);
+ _phase_timeout_cnt++;
+
+ _state->SetTimeout(0.1);
+
+ // Test timeout
+ //-------------
+ _state->InvokeTest(123, 5000);
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_TIMEOUT);
+ FastOS_Thread::Sleep(5500); // settle
+
+ _state->SetTimeout(5.0);
+}
+
+
+void TestAbort(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_ABORT);
+ _phase_abort_cnt++;
+
+ // Test abort
+ //-----------
+ _state->InvokeTestAndAbort(456, 1000);
+ EXPECT_TRUE(_state->_req->GetErrorCode() == FRTE_RPC_ABORT);
+ FastOS_Thread::Sleep(1500); // settle
+}
+
+
+void TestEcho(State *_state) {
+ ASSERT_TRUE(_state->_testPhase == PHASE_ECHO);
+ _phase_echo_cnt++;
+
+ // Test echo
+ //----------
+ _state->NewReq();
+ EXPECT_TRUE(_state->_echo.PrepareEchoReq(_state->_req));
+ _state->InvokeSync();
+ EXPECT_TRUE(!_state->_req->IsError());
+ EXPECT_TRUE(_state->_req->GetReturn()->Equals(_state->_req->GetParams()));
+}
+
+
+TEST_F("invoke test", State()) {
+ State *_state = &f1;
+
+ _state->_testPhase = PHASE_SETUP;
+ TestSetup(_state);
+
+ for (_state->_testPhase = PHASE_SIMPLE;
+ _state->_testPhase < PHASE_SHUTDOWN;
+ _state->_testPhase++) {
+
+ {
+ for (_state->_timing = TIMING_INSTANT;
+ _state->_timing < TIMING_ZZZ;
+ _state->_timing++) {
+
+ for (_state->_handling = HANDLING_SYNC;
+ _state->_handling < HANDLING_ZZZ;
+ _state->_handling++) {
+
+ switch (_state->_testPhase) {
+ case PHASE_SIMPLE:
+ if (_state->_timing == TIMING_INSTANT &&
+ _state->_handling == HANDLING_SYNC)
+ {
+ TestSimple(_state);
+ }
+ break;
+ case PHASE_VOID:
+ if (_state->_timing == TIMING_INSTANT &&
+ _state->_handling == HANDLING_SYNC)
+ {
+ TestVoid(_state);
+ }
+ break;
+ case PHASE_SPEED:
+ if (_state->_timing == TIMING_INSTANT &&
+ _state->_handling == HANDLING_SYNC)
+ {
+ TestSpeed(_state);
+ }
+ break;
+ case PHASE_ADVANCED:
+ TestAdvanced(_state);
+ break;
+ case PHASE_ERROR:
+ TestError(_state);
+ break;
+ case PHASE_TIMEOUT:
+ TestTimeout(_state);
+ break;
+ case PHASE_ABORT:
+ TestAbort(_state);
+ break;
+ case PHASE_ECHO:
+ if (_state->_timing == TIMING_INSTANT &&
+ _state->_handling == HANDLING_SYNC)
+ {
+ TestEcho(_state);
+ }
+ break;
+ default:
+ ASSERT_TRUE(false); // consult your dealer...
+ }
+ }
+ }
+ }
+ }
+ _state->_testPhase = PHASE_SHUTDOWN;
+ _state->_timing = TIMING_NULL;
+ _state->_handling = HANDLING_NULL;
+ EXPECT_TRUE(_state->WaitForDelayedReturnCount(0, 120.0));
+ _state->FreeReq();
+ _state->_client.ShutDown(true);
+ _state->_server.ShutDown(true);
+ _state->_target->SubRef();
+ _state->_target = NULL;
+ EXPECT_TRUE(_delayedReturnCnt == 0);
+ EXPECT_TRUE(_phase_simple_cnt == 1);
+ EXPECT_TRUE(_phase_void_cnt == 1);
+ EXPECT_TRUE(_phase_speed_cnt == 1);
+ EXPECT_TRUE(_phase_advanced_cnt == 4);
+ EXPECT_TRUE(_phase_error_cnt == 4);
+ EXPECT_TRUE(_phase_abort_cnt == 4);
+ EXPECT_TRUE(_phase_echo_cnt == 1);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/rpc/session.cpp b/fnet/src/tests/frt/rpc/session.cpp
new file mode 100644
index 00000000000..c39fe8cba05
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/session.cpp
@@ -0,0 +1,124 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+
+
+class Session
+{
+private:
+ static FNET_Mutex _lock;
+ static int _cnt;
+ int _val;
+
+public:
+ Session() : _val(0)
+ {
+ _lock.Lock();
+ ++_cnt;
+ _lock.Unlock();
+ }
+
+ ~Session()
+ {
+ _lock.Lock();
+ --_cnt;
+ _lock.Unlock();
+ }
+
+ void SetValue(int val) { _val = val; }
+ int GetValue() const { return _val; }
+ static int GetCnt() { return _cnt; }
+};
+
+FNET_Mutex Session::_lock;
+int Session::_cnt(0);
+
+
+struct RPC : public FRT_Invokable
+{
+ bool bogusFini;
+
+ RPC() : bogusFini(false) {}
+
+ void InitSession(FRT_RPCRequest *req)
+ {
+ Session *session = new Session();
+ req->GetConnection()->SetContext(FNET_Context((void *) session));
+ }
+
+ void FiniSession(FRT_RPCRequest *req)
+ {
+ Session *session =
+ (Session *)req->GetConnection()->GetContext()._value.VOIDP;
+ bogusFini |= (session == NULL);
+ delete session;
+ }
+
+ void GetValue(FRT_RPCRequest *req)
+ {
+ Session *session =
+ (Session *)req->GetConnection()->GetContext()._value.VOIDP;
+ req->GetReturn()->AddInt32(session->GetValue());
+ }
+
+ void SetValue(FRT_RPCRequest *req)
+ {
+ Session *session =
+ (Session *)req->GetConnection()->GetContext()._value.VOIDP;
+ session->SetValue(req->GetParams()->GetValue(0)._intval32);
+ }
+
+ void Init(FRT_Supervisor *s)
+ {
+ FRT_ReflectionBuilder rb(s);
+ rb.DefineMethod("getValue", "", "i", true,
+ FRT_METHOD(RPC::GetValue), this);
+ rb.DefineMethod("setValue", "i", "", true,
+ FRT_METHOD(RPC::SetValue), this);
+ s->SetSessionInitHook(FRT_METHOD(RPC::InitSession), this);
+ s->SetSessionFiniHook(FRT_METHOD(RPC::FiniSession), this);
+ }
+};
+
+TEST("session") {
+ RPC rpc;
+ FRT_Supervisor orb;
+ char spec[64];
+ rpc.Init(&orb);
+ ASSERT_TRUE(orb.Listen("tcp/0"));
+ sprintf(spec, "tcp/localhost:%d", orb.GetListenPort());
+ ASSERT_TRUE(orb.Start());
+
+ FRT_Target *target = orb.GetTarget(spec);
+ FRT_RPCRequest *req = orb.AllocRPCRequest();
+
+ req->SetMethodName("getValue");
+ target->InvokeSync(req, 5.0);
+ ASSERT_TRUE(!req->IsError() &&
+ strcmp(req->GetReturnSpec(), "i") == 0 &&
+ req->GetReturn()->GetValue(0)._intval32 == 0);
+
+ req = orb.AllocRPCRequest(req);
+ req->SetMethodName("setValue");
+ req->GetParams()->AddInt32(42);
+ target->InvokeSync(req, 5.0);
+ ASSERT_TRUE(!req->IsError() &&
+ strcmp(req->GetReturnSpec(), "") == 0);
+
+ req = orb.AllocRPCRequest(req);
+ req->SetMethodName("getValue");
+ target->InvokeSync(req, 5.0);
+ ASSERT_TRUE(!req->IsError() &&
+ strcmp(req->GetReturnSpec(), "i") == 0 &&
+ req->GetReturn()->GetValue(0)._intval32 == 42);
+
+ EXPECT_TRUE(Session::GetCnt() == 1);
+
+ req->SubRef();
+ target->SubRef();
+ orb.ShutDown(true);
+ EXPECT_TRUE(Session::GetCnt() == 0);
+ EXPECT_TRUE(!rpc.bogusFini);
+};
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/rpc/sharedblob.cpp b/fnet/src/tests/frt/rpc/sharedblob.cpp
new file mode 100644
index 00000000000..bb115f1c65f
--- /dev/null
+++ b/fnet/src/tests/frt/rpc/sharedblob.cpp
@@ -0,0 +1,256 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+#include <vector>
+
+struct MyBlob : FRT_ISharedBlob
+{
+ int refcnt;
+ MyBlob() : refcnt(1) {}
+ virtual uint32_t getLen() { return (strlen("blob_test") + 1); }
+ virtual const char *getData() { return "blob_test"; }
+ virtual void addRef() { ++refcnt; }
+ virtual void subRef() { --refcnt; }
+};
+
+struct Data
+{
+ enum {
+ SMALL = (FRT_MemoryTub::ALLOC_LIMIT / 2),
+ LARGE = (FRT_MemoryTub::ALLOC_LIMIT * 2)
+ };
+
+ char *buf;
+ uint32_t len;
+
+ Data(const char *pt, uint32_t l) : buf(new char[l]), len(l) {
+ memcpy(buf, pt, len);
+ }
+ Data(uint32_t l, char c) : buf(new char[l]), len(l) {
+ memset(buf, c, len);
+ }
+ Data(const Data &rhs) : buf(new char[rhs.len]), len(rhs.len) {
+ memcpy(buf, rhs.buf, len);
+ }
+ Data &operator=(const Data &rhs) {
+ if (this != &rhs) {
+ delete [] buf;
+ buf = new char[rhs.len];
+ len = rhs.len;
+ memcpy(buf, rhs.buf, len);
+ }
+ return *this;
+ }
+ bool check(uint32_t l, char c) {
+ if (l != len) {
+ fprintf(stderr, "blob length was %u, expected %u\n", len, l);
+ return false;
+ }
+ for (uint32_t i = 0; i < l; ++i) {
+ if (buf[i] != c) {
+ fprintf(stderr, "byte at offset %u was %c, expected %c\n", i, buf[i], c);
+ return false;
+ }
+ }
+ return true;
+ }
+ ~Data() {
+ delete [] buf;
+ }
+};
+
+struct DataSet
+{
+ std::vector<Data> blobs;
+
+ void sample(FRT_Values &v) {
+ blobs.push_back(Data(v.GetNumValues(), 'V'));
+ for (uint32_t i = 0; i < v.GetNumValues(); ++i) {
+ if (v.GetType(i) == FRT_VALUE_DATA) {
+ blobs.push_back(Data(1, 'x'));
+ blobs.push_back(Data(v[i]._data._buf, v[i]._data._len));
+ } else if (v.GetType(i) == FRT_VALUE_DATA_ARRAY) {
+ blobs.push_back(Data(v[i]._data_array._len, 'X'));
+ for (uint32_t j = 0; j < v[i]._data_array._len; ++j) {
+ blobs.push_back(Data(v[i]._data_array._pt[j]._buf,
+ v[i]._data_array._pt[j]._len));
+ }
+ }
+ }
+ }
+};
+
+struct ServerSampler : public FRT_Invokable
+{
+ DataSet &dataSet;
+ FRT_RPCRequest *clientReq;
+ FRT_RPCRequest *serverReq;
+
+ ServerSampler(DataSet &ds, FRT_RPCRequest *cr) : dataSet(ds), clientReq(cr), serverReq(0) {}
+
+ void RPC_test(FRT_RPCRequest *req)
+ {
+ if (clientReq != 0) {
+ dataSet.sample(*clientReq->GetParams()); // client params after drop
+ }
+
+ // store away parameters
+ FNET_DataBuffer buf;
+ buf.EnsureFree(req->GetParams()->GetLength());
+ req->GetParams()->EncodeCopy(&buf);
+
+ dataSet.sample(*req->GetParams()); // server params before drop
+ req->DiscardBlobs();
+ dataSet.sample(*req->GetParams()); // server params after drop
+
+ // restore parameters into return values
+ req->GetReturn()->DecodeCopy(&buf, buf.GetDataLen());
+
+ dataSet.sample(*req->GetReturn()); // server return before drop
+
+ // keep request to sample return after drop
+ req->AddRef();
+ serverReq = req;
+ }
+};
+
+TEST("testExplicitShared") {
+ FRT_Supervisor orb;
+ MyBlob blob;
+
+ FRT_RPCRequest *req = orb.AllocRPCRequest();
+ EXPECT_TRUE(blob.refcnt == 1);
+
+ req->GetParams()->AddSharedData(&blob);
+ req->GetParams()->AddInt32(42);
+ req->GetParams()->AddSharedData(&blob);
+ req->GetParams()->AddInt32(84);
+ req->GetParams()->AddSharedData(&blob);
+
+ EXPECT_TRUE(blob.refcnt == 4);
+ EXPECT_TRUE(strcmp(req->GetParamSpec(), "xixix") == 0);
+ EXPECT_TRUE(req->GetParams()->GetValue(0)._data._len == blob.getLen());
+ EXPECT_TRUE(req->GetParams()->GetValue(0)._data._buf == blob.getData());
+ EXPECT_TRUE(req->GetParams()->GetValue(1)._intval32 == 42);
+ EXPECT_TRUE(req->GetParams()->GetValue(2)._data._len == blob.getLen());
+ EXPECT_TRUE(req->GetParams()->GetValue(2)._data._buf == blob.getData());
+ EXPECT_TRUE(req->GetParams()->GetValue(3)._intval32 == 84);
+ EXPECT_TRUE(req->GetParams()->GetValue(4)._data._len == blob.getLen());
+ EXPECT_TRUE(req->GetParams()->GetValue(4)._data._buf == blob.getData());
+
+ req->CreateRequestPacket(true)->Free(); // fake request send.
+
+ EXPECT_TRUE(blob.refcnt == 1);
+ EXPECT_TRUE(strcmp(req->GetParamSpec(), "xixix") == 0);
+ EXPECT_TRUE(req->GetParams()->GetValue(0)._data._len == 0);
+ EXPECT_TRUE(req->GetParams()->GetValue(0)._data._buf == NULL);
+ EXPECT_TRUE(req->GetParams()->GetValue(1)._intval32 == 42);
+ EXPECT_TRUE(req->GetParams()->GetValue(2)._data._len == 0);
+ EXPECT_TRUE(req->GetParams()->GetValue(2)._data._buf == NULL);
+ EXPECT_TRUE(req->GetParams()->GetValue(3)._intval32 == 84);
+ EXPECT_TRUE(req->GetParams()->GetValue(4)._data._len == 0);
+ EXPECT_TRUE(req->GetParams()->GetValue(4)._data._buf == NULL);
+
+ req = orb.AllocRPCRequest(req);
+
+ req->GetParams()->AddSharedData(&blob);
+ req->GetParams()->AddInt32(42);
+ req->GetParams()->AddSharedData(&blob);
+ req->GetParams()->AddInt32(84);
+ req->GetParams()->AddSharedData(&blob);
+
+ EXPECT_TRUE(blob.refcnt == 4);
+ req->SubRef();
+ EXPECT_TRUE(blob.refcnt == 1);
+}
+
+TEST("testImplicitShared") {
+ DataSet dataSet;
+ FRT_Supervisor orb;
+ FRT_RPCRequest *req = orb.AllocRPCRequest();
+ ServerSampler serverSampler(dataSet, req);
+ {
+ FRT_ReflectionBuilder rb(&orb);
+ rb.DefineMethod("test", "*", "*", true,
+ FRT_METHOD(ServerSampler::RPC_test), &serverSampler);
+ }
+ orb.Listen(0);
+ int port = orb.GetListenPort();
+ ASSERT_TRUE(port != 0);
+ orb.Start();
+
+ char tmp[64];
+ snprintf(tmp, sizeof(tmp), "tcp/localhost:%d", port);
+ FRT_Target *target = orb.GetTarget(tmp);
+ req->SetMethodName("test");
+ {
+ Data data(Data::SMALL, 'a');
+ req->GetParams()->AddData(data.buf, data.len);
+ }
+ {
+ Data data(Data::LARGE, 'b');
+ req->GetParams()->AddData(data.buf, data.len);
+ }
+ {
+ char *data = req->GetParams()->AddData(Data::LARGE);
+ memset(data, 'c', Data::LARGE);
+ }
+ {
+ Data data1(Data::SMALL, 'd');
+ Data data2(Data::LARGE, 'e');
+ FRT_DataValue *arr = req->GetParams()->AddDataArray(2);
+ req->GetParams()->SetData(&arr[0], data1.buf, data1.len);
+ req->GetParams()->SetData(&arr[1], data2.buf, data2.len);
+ }
+
+ dataSet.sample(*req->GetParams()); // client params before drop
+
+ target->InvokeSync(req, 30.0);
+
+ if (serverSampler.serverReq != 0) {
+ dataSet.sample(*serverSampler.serverReq->GetReturn()); // server return after drop
+ }
+ dataSet.sample(*req->GetReturn()); // client return before drop
+
+ req->DiscardBlobs();
+
+ dataSet.sample(*req->GetReturn()); // client return after drop
+
+ // verify blob samples
+ EXPECT_EQUAL(dataSet.blobs.size(), 80u);
+
+ for (int i = 0; i < 80; i += 20) {
+ // before discard (client params, server params, server return, client return)
+ EXPECT_TRUE(dataSet.blobs[i + 0].check(4, 'V'));
+ EXPECT_TRUE(dataSet.blobs[i + 1].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 2].check(Data::SMALL, 'a'));
+ EXPECT_TRUE(dataSet.blobs[i + 3].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 4].check(Data::LARGE, 'b'));
+ EXPECT_TRUE(dataSet.blobs[i + 5].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 6].check(Data::LARGE, 'c'));
+ EXPECT_TRUE(dataSet.blobs[i + 7].check(2, 'X'));
+ EXPECT_TRUE(dataSet.blobs[i + 8].check(Data::SMALL, 'd'));
+ EXPECT_TRUE(dataSet.blobs[i + 9].check(Data::LARGE, 'e'));
+
+ // after discard (client params, server params, server return, client return)
+ EXPECT_TRUE(dataSet.blobs[i + 10].check(4, 'V'));
+ EXPECT_TRUE(dataSet.blobs[i + 11].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 12].check(Data::SMALL, 'a'));
+ EXPECT_TRUE(dataSet.blobs[i + 13].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 14].check(0, 0));
+ EXPECT_TRUE(dataSet.blobs[i + 15].check(1, 'x'));
+ EXPECT_TRUE(dataSet.blobs[i + 16].check(0, 0));
+ EXPECT_TRUE(dataSet.blobs[i + 17].check(2, 'X'));
+ EXPECT_TRUE(dataSet.blobs[i + 18].check(Data::SMALL, 'd'));
+ EXPECT_TRUE(dataSet.blobs[i + 19].check(0, 0));
+ }
+
+ if (serverSampler.serverReq != 0) {
+ serverSampler.serverReq->SubRef();
+ }
+ req->SubRef();
+ target->SubRef();
+ orb.ShutDown(true);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/frt/values/.gitignore b/fnet/src/tests/frt/values/.gitignore
new file mode 100644
index 00000000000..509dbc76bff
--- /dev/null
+++ b/fnet/src/tests/frt/values/.gitignore
@@ -0,0 +1 @@
+fnet_values_test_app
diff --git a/fnet/src/tests/frt/values/CMakeLists.txt b/fnet/src/tests/frt/values/CMakeLists.txt
new file mode 100644
index 00000000000..f1a851a09b1
--- /dev/null
+++ b/fnet/src/tests/frt/values/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_values_test_app
+ SOURCES
+ values_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_values_test_app COMMAND fnet_values_test_app)
diff --git a/fnet/src/tests/frt/values/FILES b/fnet/src/tests/frt/values/FILES
new file mode 100644
index 00000000000..8450fbfdf7e
--- /dev/null
+++ b/fnet/src/tests/frt/values/FILES
@@ -0,0 +1 @@
+values_test.cpp
diff --git a/fnet/src/tests/frt/values/values_test.cpp b/fnet/src/tests/frt/values/values_test.cpp
new file mode 100644
index 00000000000..01c43d8207d
--- /dev/null
+++ b/fnet/src/tests/frt/values/values_test.cpp
@@ -0,0 +1,207 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+
+uint8_t int8_arr[3] = { 1, 2, 3 };
+uint16_t int16_arr[3] = { 2, 4, 6 };
+uint32_t int32_arr[3] = { 4, 8, 12 };
+uint64_t int64_arr[3] = { 8, 16, 24 };
+float float_arr[3] = { 0.5, 1.0, 1.5 };
+double double_arr[3] = { 0.25, 0.50, 0.75 };
+
+template <typename T>
+void arr_cpy(T *dst, const T* src, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ dst[i] = src[i];
+ }
+}
+
+void fillValues(FRT_Values &values) {
+ {
+ values.AddInt8(int8_arr[0]);
+ arr_cpy(values.AddInt8Array(3), int8_arr, 3);
+ values.AddInt8Array(int8_arr, 3);
+ values.AddInt8ArrayRef(int8_arr, 3);
+ }
+ {
+ values.AddInt16(int16_arr[0]);
+ arr_cpy(values.AddInt16Array(3), int16_arr, 3);
+ values.AddInt16Array(int16_arr, 3);
+ values.AddInt16ArrayRef(int16_arr, 3);
+ }
+ {
+ values.AddInt32(int32_arr[0]);
+ arr_cpy(values.AddInt32Array(3), int32_arr, 3);
+ values.AddInt32Array(int32_arr, 3);
+ values.AddInt32ArrayRef(int32_arr, 3);
+ }
+ {
+ values.AddInt64(int64_arr[0]);
+ arr_cpy(values.AddInt64Array(3), int64_arr, 3);
+ values.AddInt64Array(int64_arr, 3);
+ values.AddInt64ArrayRef(int64_arr, 3);
+ }
+ {
+ values.AddFloat(float_arr[0]);
+ arr_cpy(values.AddFloatArray(3), float_arr, 3);
+ values.AddFloatArray(float_arr, 3);
+ values.AddFloatArrayRef(float_arr, 3);
+ }
+ {
+ values.AddDouble(double_arr[0]);
+ arr_cpy(values.AddDoubleArray(3), double_arr, 3);
+ values.AddDoubleArray(double_arr, 3);
+ values.AddDoubleArrayRef(double_arr, 3);
+ }
+ {
+ values.AddString("foo");
+ values.AddString("bar", 3);
+ strcpy(values.AddString(3), "baz");
+ FRT_StringValue *str_arr = values.AddStringArray(3);
+ values.SetString(str_arr, "foo");
+ values.SetString(str_arr + 1, "bar");
+ values.SetString(str_arr + 2, "baz", 3);
+ }
+ {
+ values.AddData("foo", 3);
+ strncpy(values.AddData(3), "bar", 3);
+ FRT_DataValue *data_arr = values.AddDataArray(3);
+ values.SetData(data_arr, "foo", 3);
+ values.SetData(data_arr + 1, "bar", 3);
+ values.SetData(data_arr + 2, "baz", 3);
+ }
+}
+
+void checkValues(FRT_Values &values) {
+ ASSERT_EQUAL(31u, values.GetNumValues());
+ ASSERT_EQUAL(std::string("bBBBhHHHiIIIlLLLfFFFdDDDsssSxxX"), values.GetTypeString());
+ size_t idx = 0;
+ EXPECT_EQUAL(int8_arr[0], values[idx++]._intval8);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._int8_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(int8_arr[j], values[idx]._int8_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(int16_arr[0], values[idx++]._intval16);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._int16_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(int16_arr[j], values[idx]._int16_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(int32_arr[0], values[idx++]._intval32);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._int32_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(int32_arr[j], values[idx]._int32_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(int64_arr[0], values[idx++]._intval64);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._int64_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(int64_arr[j], values[idx]._int64_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(float_arr[0], values[idx++]._float);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._float_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(float_arr[j], values[idx]._float_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(double_arr[0], values[idx++]._double);
+ for (size_t i = 0; i < 3; ++i, ++idx) {
+ ASSERT_EQUAL(3u, values[idx]._double_array._len);
+ for (size_t j = 0; j < 3; ++j) {
+ EXPECT_EQUAL(double_arr[j], values[idx]._double_array._pt[j]);
+ }
+ }
+ EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._string._str,
+ values[idx]._string._len));
+ ++idx;
+ EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._string._str,
+ values[idx]._string._len));
+ ++idx;
+ EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._string._str,
+ values[idx]._string._len));
+ ++idx;
+ ASSERT_EQUAL(3u, values[idx]._string_array._len);
+ EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._string_array._pt[0]._str,
+ values[idx]._string_array._pt[0]._len));
+ EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._string_array._pt[1]._str,
+ values[idx]._string_array._pt[1]._len));
+ EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._string_array._pt[2]._str,
+ values[idx]._string_array._pt[2]._len));
+ ++idx;
+ EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._data._buf,
+ values[idx]._data._len));
+ ++idx;
+ EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._data._buf,
+ values[idx]._data._len));
+ ++idx;
+ ASSERT_EQUAL(3u, values[idx]._data_array._len);
+ EXPECT_EQUAL(std::string("foo"), std::string(values[idx]._data_array._pt[0]._buf,
+ values[idx]._data_array._pt[0]._len));
+ EXPECT_EQUAL(std::string("bar"), std::string(values[idx]._data_array._pt[1]._buf,
+ values[idx]._data_array._pt[1]._len));
+ EXPECT_EQUAL(std::string("baz"), std::string(values[idx]._data_array._pt[2]._buf,
+ values[idx]._data_array._pt[2]._len));
+ ++idx;
+ EXPECT_EQUAL(31u, idx);
+}
+
+void checkValues(FRT_Values &v1, FRT_Values &v2) {
+ checkValues(v1);
+ checkValues(v2);
+ EXPECT_TRUE(v1.Equals(&v2));
+ EXPECT_TRUE(v2.Equals(&v1));
+}
+
+TEST_FF("set and get", FRT_MemoryTub(), FRT_Values(&f1)) {
+ fillValues(f2);
+ checkValues(f2);
+}
+
+TEST_FFFF("encode/decode big endian", FRT_MemoryTub(), FRT_Values(&f1),
+ FNET_DataBuffer(), FRT_Values(&f1))
+{
+ fillValues(f2);
+ f2.EncodeBig(&f3);
+ EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen());
+ EXPECT_TRUE(f4.DecodeBig(&f3, f3.GetDataLen()));
+ checkValues(f2, f4);
+}
+
+TEST_FFFF("encode/decode host endian", FRT_MemoryTub(), FRT_Values(&f1),
+ FNET_DataBuffer(), FRT_Values(&f1))
+{
+ fillValues(f2);
+ f2.EncodeCopy(&f3);
+ EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen());
+ EXPECT_TRUE(f4.DecodeCopy(&f3, f3.GetDataLen()));
+ checkValues(f2, f4);
+}
+
+TEST_FFFF("decode little if host is little", FRT_MemoryTub(), FRT_Values(&f1),
+ FNET_DataBuffer(), FRT_Values(&f1))
+{
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE) {
+ fprintf(stderr, "little endian detected...\n");
+ fillValues(f2);
+ f2.EncodeCopy(&f3);
+ EXPECT_EQUAL(f2.GetLength(), f3.GetDataLen());
+ EXPECT_TRUE(f4.DecodeLittle(&f3, f3.GetDataLen()));
+ checkValues(f2, f4);
+ } else {
+ fprintf(stderr, "host is not little endian, coverage will suffer...\n");
+ }
+}
+
+TEST_FF("print values", FRT_MemoryTub(), FRT_Values(&f1)) {
+ fillValues(f2);
+ f2.Print();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/info/.gitignore b/fnet/src/tests/info/.gitignore
new file mode 100644
index 00000000000..bd20557beb5
--- /dev/null
+++ b/fnet/src/tests/info/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+info_test
+fnet_info_test_app
diff --git a/fnet/src/tests/info/CMakeLists.txt b/fnet/src/tests/info/CMakeLists.txt
new file mode 100644
index 00000000000..fb6069e5f8a
--- /dev/null
+++ b/fnet/src/tests/info/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_info_test_app
+ SOURCES
+ info.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_info_test_app COMMAND fnet_info_test_app)
diff --git a/fnet/src/tests/info/DESC b/fnet/src/tests/info/DESC
new file mode 100644
index 00000000000..4e7cd423af6
--- /dev/null
+++ b/fnet/src/tests/info/DESC
@@ -0,0 +1 @@
+Dummy test used to print out some general info about FNET.
diff --git a/fnet/src/tests/info/FILES b/fnet/src/tests/info/FILES
new file mode 100644
index 00000000000..62e5403ecb5
--- /dev/null
+++ b/fnet/src/tests/info/FILES
@@ -0,0 +1 @@
+info.cpp
diff --git a/fnet/src/tests/info/info.cpp b/fnet/src/tests/info/info.cpp
new file mode 100644
index 00000000000..284be22db63
--- /dev/null
+++ b/fnet/src/tests/info/info.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+#include <mutex>
+#include <condition_variable>
+
+struct RPC : public FRT_Invokable
+{
+ void GetInfo(FRT_RPCRequest *req)
+ {
+ req->GetReturn()->AddString("fastos X current");
+ req->GetReturn()->AddString(FNET_Info::GetFNETVersion());
+ const char *endian_str = "UNKNOWN";
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
+ endian_str = "LITTLE";
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_BIG)
+ endian_str = "BIG";
+ req->GetReturn()->AddString(endian_str);
+ req->GetReturn()->AddInt32(FD_SETSIZE);
+ req->GetReturn()->AddInt32(sizeof(FRT_RPCRequest));
+ }
+
+ void Init(FRT_Supervisor *s)
+ {
+ FRT_ReflectionBuilder rb(s);
+ //-------------------------------------------------------------------
+ rb.DefineMethod("getInfo", "", "sssii", true,
+ FRT_METHOD(RPC::GetInfo), this);
+ // FastOS version
+ // FNET version
+ // endian
+ // FD_SETSIZE
+ // req object size
+ //-------------------------------------------------------------------
+ }
+};
+
+TEST("info") {
+ RPC rpc;
+ FRT_Supervisor orb;
+ char spec[64];
+ rpc.Init(&orb);
+ ASSERT_TRUE(orb.Listen("tcp/0"));
+ sprintf(spec, "tcp/localhost:%d", orb.GetListenPort());
+ ASSERT_TRUE(orb.Start());
+
+ FRT_Target *target = orb.GetTarget(spec);
+ FRT_RPCRequest *local_info = orb.AllocRPCRequest();
+ FRT_RPCRequest *remote_info = orb.AllocRPCRequest();
+
+ rpc.GetInfo(local_info);
+ remote_info->SetMethodName("getInfo");
+ target->InvokeSync(remote_info, 10.0);
+ EXPECT_FALSE(remote_info->IsError());
+
+ FRT_Values &l = *local_info->GetReturn();
+ // FRT_Values &r = *remote_info->GetReturn();
+
+ fprintf(stderr, "FastOS Version: %s\n", l[0]._string._str);
+ fprintf(stderr, "FNET Version: %s\n", l[1]._string._str);
+ fprintf(stderr, "Endian: %s\n", l[2]._string._str);
+ fprintf(stderr, "FD_SETSIZE: %d\n", l[3]._intval32);
+ fprintf(stderr, "sizeof(FRT_RPCRequest): %d\n", l[4]._intval32);
+
+ target->SubRef();
+ local_info->SubRef();
+ remote_info->SubRef();
+ orb.ShutDown(true);
+};
+
+TEST("size of important objects")
+{
+ EXPECT_EQUAL(184u, sizeof(FNET_IOComponent));
+ EXPECT_EQUAL(32u, sizeof(FNET_Channel));
+ EXPECT_EQUAL(40u, sizeof(FNET_PacketQueue_NoLock));
+ EXPECT_EQUAL(512u, sizeof(FNET_Connection));
+ EXPECT_EQUAL(96u, sizeof(FNET_Cond));
+ EXPECT_EQUAL(48u, sizeof(FNET_DataBuffer));
+ EXPECT_EQUAL(24u, sizeof(FastOS_Time));
+ EXPECT_EQUAL(8u, sizeof(FNET_Context));
+ EXPECT_EQUAL(8u, sizeof(fastos::TimeStamp));
+ EXPECT_EQUAL(48u, sizeof(FastOS_Mutex));
+ EXPECT_EQUAL(40u, sizeof(pthread_mutex_t));
+ EXPECT_EQUAL(48u, sizeof(pthread_cond_t));
+ EXPECT_EQUAL(40u, sizeof(std::mutex));
+ EXPECT_EQUAL(48u, sizeof(std::condition_variable));
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/.gitignore b/fnet/src/tests/locking/.gitignore
new file mode 100644
index 00000000000..dc9c395ba5b
--- /dev/null
+++ b/fnet/src/tests/locking/.gitignore
@@ -0,0 +1,8 @@
+.depend
+Makefile
+castspeed_test
+drainpackets_test
+lockspeed_test
+fnet_castspeed_test_app
+fnet_drainpackets_test_app
+fnet_lockspeed_test_app
diff --git a/fnet/src/tests/locking/CMakeLists.txt b/fnet/src/tests/locking/CMakeLists.txt
new file mode 100644
index 00000000000..7a0187717e6
--- /dev/null
+++ b/fnet/src/tests/locking/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_drainpackets_test_app
+ SOURCES
+ drainpackets.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_drainpackets_test_app NO_VALGRIND COMMAND fnet_drainpackets_test_app)
+vespa_add_executable(fnet_lockspeed_test_app
+ SOURCES
+ lockspeed.cpp
+ dummy.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_lockspeed_test_app NO_VALGRIND COMMAND fnet_lockspeed_test_app)
+vespa_add_executable(fnet_castspeed_test_app
+ SOURCES
+ castspeed.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_castspeed_test_app NO_VALGRIND COMMAND fnet_castspeed_test_app)
diff --git a/fnet/src/tests/locking/DESC b/fnet/src/tests/locking/DESC
new file mode 100644
index 00000000000..86c035e5a09
--- /dev/null
+++ b/fnet/src/tests/locking/DESC
@@ -0,0 +1 @@
+Benchmark locking and some queue locking strategies.
diff --git a/fnet/src/tests/locking/FILES b/fnet/src/tests/locking/FILES
new file mode 100644
index 00000000000..dfa689e7256
--- /dev/null
+++ b/fnet/src/tests/locking/FILES
@@ -0,0 +1,2 @@
+lockspeed.cpp
+drainpackets.cpp
diff --git a/fnet/src/tests/locking/castspeed.cpp b/fnet/src/tests/locking/castspeed.cpp
new file mode 100644
index 00000000000..874769a7579
--- /dev/null
+++ b/fnet/src/tests/locking/castspeed.cpp
@@ -0,0 +1,219 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+class B;
+
+static int taken = 0;
+extern void takeB(B* foo) __attribute__((noinline));
+
+class A
+{
+public:
+ virtual B* asB() { return 0; }
+ virtual ~A() {}
+};
+
+class C: public A
+{
+public:
+ B *otherB;
+ virtual B* asB() { return otherB; }
+ C() : otherB(NULL) {}
+};
+
+class B: public C
+{
+public:
+ virtual B* asB() { return this; }
+};
+
+
+class CastTest
+{
+ A* myB;
+ B* realB;
+public:
+ B* DummyCast() {
+ return realB;
+ }
+ B* DynamicCast() {
+ return dynamic_cast<B*>(myB);
+ }
+ B* TypesafeCast() {
+ return myB->asB();
+ }
+ B* UnsafeCast() {
+ return reinterpret_cast<B*>(myB);
+ }
+ B* StaticCast() {
+ return static_cast<B*>(myB);
+ }
+
+ CastTest() {
+ myB = realB = new B;
+ }
+
+ ~CastTest() {
+ delete myB;
+ }
+};
+
+#define LOOPCNT 30000000
+
+TEST("cast speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ CastTest casttest;
+
+ double actualTime;
+ uint32_t i;
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dummy cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ takeB(casttest.DynamicCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dynamic cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ takeB(casttest.TypesafeCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d typesafe cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ takeB(casttest.StaticCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d static cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ takeB(casttest.UnsafeCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d reinterpret_cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+
+ taken = 0;
+ start.SetNow();
+ for (i = 0; i < LOOPCNT; i++) {
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ takeB(casttest.DummyCast());
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+ fprintf(stderr,
+ "%d dummy cast calls: %f ms (%1.2f/us) [%f]\n",
+ taken, stop.MilliSecs(),
+ 0.001 * taken / stop.MilliSecs(),
+ actualTime);
+}
+
+void takeB(B* foo)
+{
+ if (foo != 0) {
+ taken++;
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/drainpackets.cpp b/fnet/src/tests/locking/drainpackets.cpp
new file mode 100644
index 00000000000..066923f0c70
--- /dev/null
+++ b/fnet/src/tests/locking/drainpackets.cpp
@@ -0,0 +1,134 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+
+class MyPacket : public FNET_Packet
+{
+public:
+ uint32_t GetPCODE() { return 0; }
+ uint32_t GetLength() { return 0; }
+ void Encode(FNET_DataBuffer *) {}
+ bool Decode(FNET_DataBuffer *, uint32_t)
+ { return true; }
+};
+
+
+TEST("drain packets") {
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ FNET_Mutex lock;
+
+ FNET_PacketQueue q1(512);
+ FNET_PacketQueue q2(512);
+ FNET_PacketQueue q3(512);
+
+ int i;
+
+ // create dummy packets
+
+ for (i = 0; i < 500; i++) {
+ q1.QueuePacket_NoLock(new MyPacket(), FNET_Context());
+ }
+
+ // drain packets directly with single lock interval
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ lock.Lock();
+
+ while (!q1.IsEmpty_NoLock()) {
+ packet = q1.DequeuePacket_NoLock(&context);
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ lock.Unlock();
+
+ //------------------------
+
+ lock.Lock();
+
+ while (!q3.IsEmpty_NoLock()) {
+ packet = q3.DequeuePacket_NoLock(&context);
+ q1.QueuePacket_NoLock(packet, context);
+ }
+
+ lock.Unlock();
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct, single lock interval (10M packets): %1.2f ms\n",
+ stop.MilliSecs());
+
+ // flush packets, then move without lock
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ lock.Lock();
+ q1.FlushPackets_NoLock(&q2);
+ lock.Unlock();
+
+ while (!q2.IsEmpty_NoLock()) {
+ packet = q2.DequeuePacket_NoLock(&context);
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ //------------------------
+
+ lock.Lock();
+ q3.FlushPackets_NoLock(&q2);
+ lock.Unlock();
+
+ while (!q2.IsEmpty_NoLock()) {
+ packet = q2.DequeuePacket_NoLock(&context);
+ q1.QueuePacket_NoLock(packet, context);
+ }
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "indirect (10M packets): %1.2f ms\n", stop.MilliSecs());
+
+ // drain packets directly with multiple lock intervals
+
+ start.SetNow();
+
+ for (i = 0; i < 10000; i++) {
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ while ((packet = q1.DequeuePacket(0, &context)) != NULL) {
+ q3.QueuePacket_NoLock(packet, context);
+ }
+
+ //------------------------
+
+ while ((packet = q3.DequeuePacket(0, &context)) != NULL) {
+ q1.QueuePacket_NoLock(packet, context);
+ }
+ }
+
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "direct, multiple lock intervals (10M packets): %1.2f ms\n",
+ stop.MilliSecs());
+
+ EXPECT_TRUE(q1.GetPacketCnt_NoLock() == 500 &&
+ q2.GetPacketCnt_NoLock() == 0 &&
+ q3.GetPacketCnt_NoLock() == 0);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/locking/dummy.cpp b/fnet/src/tests/locking/dummy.cpp
new file mode 100644
index 00000000000..bab18ef3db9
--- /dev/null
+++ b/fnet/src/tests/locking/dummy.cpp
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include "dummy.h"
+
+DummyObj::DummyObj() {}
+DummyObj::~DummyObj() {}
+
+void DummyLock::Lock() {}
+void DummyLock::Unlock() {}
diff --git a/fnet/src/tests/locking/dummy.h b/fnet/src/tests/locking/dummy.h
new file mode 100644
index 00000000000..9a3578b43e5
--- /dev/null
+++ b/fnet/src/tests/locking/dummy.h
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+class DummyObj
+{
+public:
+ DummyObj();
+ ~DummyObj();
+};
+
+class DummyLock
+{
+public:
+ void Lock();
+ void Unlock();
+};
+
diff --git a/fnet/src/tests/locking/lockspeed.cpp b/fnet/src/tests/locking/lockspeed.cpp
new file mode 100644
index 00000000000..bccedc61b8e
--- /dev/null
+++ b/fnet/src/tests/locking/lockspeed.cpp
@@ -0,0 +1,177 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+#include "dummy.h"
+
+TEST("lock speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+ DummyLock dummy;
+ FNET_Mutex lock;
+ double dummyTime;
+ double actualTime;
+ double overhead;
+ uint32_t i;
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ dummy.Lock();
+ dummy.Unlock();
+ }
+ stop.SetNow();
+ stop -= start;
+ dummyTime = stop.MilliSecs();
+
+ fprintf(stderr,
+ "10M dummy lock/unlock: %f ms (%1.2f/ms)\n",
+ dummyTime, 10000000.0 / dummyTime);
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ lock.Lock();
+ lock.Unlock();
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+
+ fprintf(stderr,
+ "10M actual lock/unlock: %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ overhead = (actualTime - dummyTime) / 10000.0;
+
+ fprintf(stderr,
+ "approx overhead per lock/unlock: %f microseconds\n",
+ overhead);
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ FNET_Mutex lock0;
+ FNET_Mutex lock1;
+ FNET_Mutex lock2;
+ FNET_Mutex lock3;
+ FNET_Mutex lock4;
+ FNET_Mutex lock5;
+ FNET_Mutex lock6;
+ FNET_Mutex lock7;
+ FNET_Mutex lock8;
+ FNET_Mutex lock9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M mutex create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ FNET_Cond cond0;
+ FNET_Cond cond1;
+ FNET_Cond cond2;
+ FNET_Cond cond3;
+ FNET_Cond cond4;
+ FNET_Cond cond5;
+ FNET_Cond cond6;
+ FNET_Cond cond7;
+ FNET_Cond cond8;
+ FNET_Cond cond9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M cond create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ DummyObj dummy0;
+ DummyObj dummy1;
+ DummyObj dummy2;
+ DummyObj dummy3;
+ DummyObj dummy4;
+ DummyObj dummy5;
+ DummyObj dummy6;
+ DummyObj dummy7;
+ DummyObj dummy8;
+ DummyObj dummy9;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M dummy create/destroy %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+
+ start.SetNow();
+ for (i = 0; i < 1000000; i++) {
+ DummyObj *dummy0 = new DummyObj();
+ DummyObj *dummy1 = new DummyObj();
+ DummyObj *dummy2 = new DummyObj();
+ DummyObj *dummy3 = new DummyObj();
+ DummyObj *dummy4 = new DummyObj();
+ DummyObj *dummy5 = new DummyObj();
+ DummyObj *dummy6 = new DummyObj();
+ DummyObj *dummy7 = new DummyObj();
+ DummyObj *dummy8 = new DummyObj();
+ DummyObj *dummy9 = new DummyObj();
+ delete dummy9;
+ delete dummy8;
+ delete dummy7;
+ delete dummy6;
+ delete dummy5;
+ delete dummy4;
+ delete dummy3;
+ delete dummy2;
+ delete dummy1;
+ delete dummy0;
+ }
+ stop.SetNow();
+ stop -= start;
+ fprintf(stderr, "10M dummy new/delete %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 10000000.0 / stop.MilliSecs());
+
+ //---------------------------------------------------------------------------
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/printstuff/.gitignore b/fnet/src/tests/printstuff/.gitignore
new file mode 100644
index 00000000000..bfb8f2d1754
--- /dev/null
+++ b/fnet/src/tests/printstuff/.gitignore
@@ -0,0 +1 @@
+fnet_printstuff_test_app
diff --git a/fnet/src/tests/printstuff/CMakeLists.txt b/fnet/src/tests/printstuff/CMakeLists.txt
new file mode 100644
index 00000000000..7180d2866f0
--- /dev/null
+++ b/fnet/src/tests/printstuff/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_printstuff_test_app
+ SOURCES
+ printstuff_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_printstuff_test_app COMMAND fnet_printstuff_test_app)
diff --git a/fnet/src/tests/printstuff/FILES b/fnet/src/tests/printstuff/FILES
new file mode 100644
index 00000000000..95a889d6494
--- /dev/null
+++ b/fnet/src/tests/printstuff/FILES
@@ -0,0 +1 @@
+printstuff_test.cpp
diff --git a/fnet/src/tests/printstuff/printstuff_test.cpp b/fnet/src/tests/printstuff/printstuff_test.cpp
new file mode 100644
index 00000000000..3778cef9c8c
--- /dev/null
+++ b/fnet/src/tests/printstuff/printstuff_test.cpp
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/frt/frt.h>
+
+void printError(uint32_t ecode) {
+ fprintf(stderr, "error(%u): %s: %s\n",
+ ecode, FRT_GetErrorCodeName(ecode), FRT_GetDefaultErrorMessage(ecode));
+}
+
+TEST("frt error code names and default messages") {
+ printError(0);
+ printError(99);
+ for (uint32_t i = 100; i < 112; ++i) {
+ printError(i);
+ }
+ printError(198);
+ printError(199);
+ printError(200);
+ printError(70000);
+}
+
+TEST("rpc packets in a queue") {
+ FRT_RPCRequest *req = new FRT_RPCRequest();
+ {
+ req->SetMethodName("foo");
+ FNET_PacketQueue_NoLock q1(1, FNET_IPacketHandler::FNET_KEEP_CHANNEL);
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCRequestPacket(req, 0, false), FNET_Context());
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCReplyPacket(req, 0, false), FNET_Context());
+ q1.QueuePacket_NoLock(new (req->GetMemoryTub()) FRT_RPCErrorPacket(req, 0, false), FNET_Context());
+ q1.Print();
+ FNET_PacketQueue q2(2, FNET_IPacketHandler::FNET_KEEP_CHANNEL);
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCRequestPacket(req, 0, false), FNET_Context());
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCReplyPacket(req, 0, false), FNET_Context());
+ q2.QueuePacket(new (req->GetMemoryTub()) FRT_RPCErrorPacket(req, 0, false), FNET_Context());
+ q2.Print();
+ }
+ req->SubRef();
+}
+
+TEST("info") {
+ FNET_Info::PrintInfo();
+ FNET_Info::LogInfo();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/regress/databuffer/.gitignore b/fnet/src/tests/regress/databuffer/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/databuffer/.gitignore
diff --git a/fnet/src/tests/regress/fdselector/.gitignore b/fnet/src/tests/regress/fdselector/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/fdselector/.gitignore
diff --git a/fnet/src/tests/regress/frt/memorytub/.gitignore b/fnet/src/tests/regress/frt/memorytub/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/memorytub/.gitignore
diff --git a/fnet/src/tests/regress/frt/method_pt/.gitignore b/fnet/src/tests/regress/frt/method_pt/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/method_pt/.gitignore
diff --git a/fnet/src/tests/regress/frt/rpc/.gitignore b/fnet/src/tests/regress/frt/rpc/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/rpc/.gitignore
diff --git a/fnet/src/tests/regress/frt/values/.gitignore b/fnet/src/tests/regress/frt/values/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/frt/values/.gitignore
diff --git a/fnet/src/tests/regress/info/.gitignore b/fnet/src/tests/regress/info/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/info/.gitignore
diff --git a/fnet/src/tests/regress/locking/.gitignore b/fnet/src/tests/regress/locking/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/locking/.gitignore
diff --git a/fnet/src/tests/regress/scheduling/.gitignore b/fnet/src/tests/regress/scheduling/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/scheduling/.gitignore
diff --git a/fnet/src/tests/regress/spiral/.gitignore b/fnet/src/tests/regress/spiral/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/spiral/.gitignore
diff --git a/fnet/src/tests/regress/sync_execute/.gitignore b/fnet/src/tests/regress/sync_execute/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/sync_execute/.gitignore
diff --git a/fnet/src/tests/regress/thread_id/.gitignore b/fnet/src/tests/regress/thread_id/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/thread_id/.gitignore
diff --git a/fnet/src/tests/regress/time/.gitignore b/fnet/src/tests/regress/time/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/tests/regress/time/.gitignore
diff --git a/fnet/src/tests/scheduling/.gitignore b/fnet/src/tests/scheduling/.gitignore
new file mode 100644
index 00000000000..b6dccb18324
--- /dev/null
+++ b/fnet/src/tests/scheduling/.gitignore
@@ -0,0 +1,6 @@
+.depend
+Makefile
+schedule_test
+sloweventloop_test
+fnet_schedule_test_app
+fnet_sloweventloop_test_app
diff --git a/fnet/src/tests/scheduling/CMakeLists.txt b/fnet/src/tests/scheduling/CMakeLists.txt
new file mode 100644
index 00000000000..244211a7cb6
--- /dev/null
+++ b/fnet/src/tests/scheduling/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_schedule_test_app
+ SOURCES
+ schedule.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_schedule_test_app COMMAND fnet_schedule_test_app)
+vespa_add_executable(fnet_sloweventloop_test_app
+ SOURCES
+ sloweventloop.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_sloweventloop_test_app COMMAND fnet_sloweventloop_test_app)
diff --git a/fnet/src/tests/scheduling/DESC b/fnet/src/tests/scheduling/DESC
new file mode 100644
index 00000000000..6e4ca2972b9
--- /dev/null
+++ b/fnet/src/tests/scheduling/DESC
@@ -0,0 +1 @@
+Scheduler test.
diff --git a/fnet/src/tests/scheduling/FILES b/fnet/src/tests/scheduling/FILES
new file mode 100644
index 00000000000..bd5dc7f2572
--- /dev/null
+++ b/fnet/src/tests/scheduling/FILES
@@ -0,0 +1 @@
+schedule.cpp
diff --git a/fnet/src/tests/scheduling/schedule.cpp b/fnet/src/tests/scheduling/schedule.cpp
new file mode 100644
index 00000000000..0625b83e121
--- /dev/null
+++ b/fnet/src/tests/scheduling/schedule.cpp
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+FastOS_Time _time;
+FNET_Scheduler *_scheduler;
+
+
+class MyTask : public FNET_Task
+{
+public:
+ FastOS_Time _time;
+ int _target;
+ bool _done;
+
+ MyTask(int target)
+ : FNET_Task(::_scheduler),
+ _time(),
+ _target(target),
+ _done(false) {}
+
+ int GetTarget() const { return _target; }
+
+ bool Check() const
+ {
+ int a = _target;
+ int b = (int)_time.MilliSecs();
+
+ if (!_done)
+ return false;
+
+ if (b < a)
+ return false;
+
+ if ((b - a) > (2 * FNET_Scheduler::SLOT_TICK))
+ return false;
+
+ return true;
+ }
+
+ void PerformTask()
+ {
+ _time = ::_time;
+ _done = true;
+ }
+};
+
+
+class RealTimeTask : public FNET_Task
+{
+public:
+ uint32_t _cnt;
+
+ RealTimeTask() : FNET_Task(::_scheduler), _cnt(0)
+ {
+ }
+
+ uint32_t GetCnt() { return _cnt; }
+
+ void PerformTask()
+ {
+ _cnt++;
+ ScheduleNow(); // re-schedule as fast as possible
+ }
+};
+
+
+TEST("schedule") {
+ _time.SetMilliSecs(0);
+ _scheduler = new FNET_Scheduler(&_time, &_time);
+
+ RealTimeTask rt_task1;
+ RealTimeTask rt_task2;
+ RealTimeTask rt_task3;
+ rt_task1.ScheduleNow();
+ rt_task2.ScheduleNow();
+ rt_task3.ScheduleNow();
+
+ uint32_t taskCnt = 1000000;
+ MyTask **tasks = new MyTask*[taskCnt];
+ assert(tasks != NULL);
+ for (uint32_t i = 0; i < taskCnt; i++) {
+ tasks[i] = new MyTask(rand() & 131071);
+ assert(tasks[i] != NULL);
+ }
+
+ FastOS_Time start;
+ FastOS_Time stop;
+
+ start.SetNow();
+ for (uint32_t j = 0; j < taskCnt; j++) {
+ tasks[j]->Schedule(tasks[j]->GetTarget() / 1000.0);
+ }
+ stop.SetNow();
+ stop -= start;
+ double scheduleTime = stop.MilliSecs() / (double)taskCnt;
+ fprintf(stderr, "scheduling cost: %1.2f microseconds\n", scheduleTime * 1000.0);
+
+ start.SetNow();
+ uint32_t tickCnt = 0;
+ while (_time.MilliSecs() < 135000.0) {
+ _time.AddMilliSecs(FNET_Scheduler::SLOT_TICK);
+ _scheduler->CheckTasks();
+ tickCnt++;
+ }
+ stop.SetNow();
+ stop -= start;
+ double runTime = stop.MilliSecs();
+ fprintf(stderr, "3 RT tasks + %d one-shot tasks over 135s\n", taskCnt);
+ fprintf(stderr, "%1.2f seconds actual run time\n", runTime / 1000.0);
+ fprintf(stderr, "%1.2f tasks per simulated second\n", (double)taskCnt / (double)135);
+ fprintf(stderr, "%d ticks\n", tickCnt);
+ fprintf(stderr, "%1.2f %% simulated CPU usage\n", 100 * (runTime / 135000.0));
+ fprintf(stderr, "%1.2f microseconds per performed task\n",
+ 1000.0 * (runTime / (taskCnt + tickCnt * 3.0)));
+
+ for (uint32_t k = 0; k < taskCnt; k++) {
+ EXPECT_TRUE(tasks[k]->Check());
+ }
+ EXPECT_TRUE(rt_task1.GetCnt() == tickCnt);
+ EXPECT_TRUE(rt_task2.GetCnt() == tickCnt);
+ EXPECT_TRUE(rt_task3.GetCnt() == tickCnt);
+
+ for (uint32_t l = 0; l < taskCnt; l++) {
+ delete tasks[l];
+ }
+ rt_task1.Kill();
+ rt_task2.Kill();
+ rt_task3.Kill();
+ delete [] tasks;
+ delete _scheduler;
+
+ { // trigger warning from scheduler destructor
+
+ FNET_Scheduler *s = new FNET_Scheduler();
+
+ FNET_Task t1(s);
+ FNET_Task t2(s);
+ FNET_Task t3(s);
+ FNET_Task t4(s);
+ FNET_Task t5(s);
+
+ t1.ScheduleNow();
+ t2.Schedule(5.0);
+ t3.Schedule(5.0);
+ t4.Schedule(10.0);
+ t5.Schedule(15.0);
+
+ delete s;
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/scheduling/sloweventloop.cpp b/fnet/src/tests/scheduling/sloweventloop.cpp
new file mode 100644
index 00000000000..3e27bfef131
--- /dev/null
+++ b/fnet/src/tests/scheduling/sloweventloop.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+
+class MyTask : public FNET_Task
+{
+public:
+ bool _done;
+
+ MyTask(FNET_Scheduler &scheduler)
+ : FNET_Task(&scheduler),
+ _done(false) {}
+
+ bool done() const { return _done; }
+ void PerformTask() { _done = true; }
+};
+
+
+TEST("slow event loop") {
+ FastOS_Time t;
+ t.SetMilliSecs(0);
+
+ FNET_Scheduler scheduler(&t, &t);
+ MyTask task(scheduler);
+ MyTask task2(scheduler);
+
+ scheduler.CheckTasks();
+ t.AddMilliSecs(10000);
+ task.Schedule(5.0);
+
+ uint32_t cnt = 0;
+ for (;;) {
+ scheduler.CheckTasks();
+ if (task.done()) {
+ break;
+ }
+ ++cnt;
+ t.AddMilliSecs(1);
+ }
+
+ if (!EXPECT_TRUE(cnt > 4700 && cnt < 4800)) {
+ fprintf(stderr, "cnt=%d\n", cnt);
+ }
+
+ scheduler.CheckTasks();
+ t.AddMilliSecs(10000);
+ task2.Schedule(5.0);
+
+ uint32_t cnt2 = 0;
+ for(;;) {
+ scheduler.CheckTasks();
+ if (task2.done()) {
+ break;
+ }
+ ++cnt2;
+ t.AddMilliSecs(10000);
+ }
+
+ if (!EXPECT_TRUE(cnt2 > 15 && cnt2 < 25)) {
+ fprintf(stderr, "cnt2=%d\n", cnt2);
+ }
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/sync_execute/.gitignore b/fnet/src/tests/sync_execute/.gitignore
new file mode 100644
index 00000000000..90d51f31d97
--- /dev/null
+++ b/fnet/src/tests/sync_execute/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+sync_execute_test
+fnet_sync_execute_test_app
diff --git a/fnet/src/tests/sync_execute/CMakeLists.txt b/fnet/src/tests/sync_execute/CMakeLists.txt
new file mode 100644
index 00000000000..820cdb1af86
--- /dev/null
+++ b/fnet/src/tests/sync_execute/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_sync_execute_test_app
+ SOURCES
+ sync_execute.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_sync_execute_test_app COMMAND fnet_sync_execute_test_app)
diff --git a/fnet/src/tests/sync_execute/DESC b/fnet/src/tests/sync_execute/DESC
new file mode 100644
index 00000000000..4f1b88599a8
--- /dev/null
+++ b/fnet/src/tests/sync_execute/DESC
@@ -0,0 +1 @@
+Test the sync and execute methods on the transport object.
diff --git a/fnet/src/tests/sync_execute/FILES b/fnet/src/tests/sync_execute/FILES
new file mode 100644
index 00000000000..2e03de91167
--- /dev/null
+++ b/fnet/src/tests/sync_execute/FILES
@@ -0,0 +1 @@
+sync_execute.cpp
diff --git a/fnet/src/tests/sync_execute/sync_execute.cpp b/fnet/src/tests/sync_execute/sync_execute.cpp
new file mode 100644
index 00000000000..e24d63effd5
--- /dev/null
+++ b/fnet/src/tests/sync_execute/sync_execute.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/sync.h>
+
+struct DoIt : public FNET_IExecutable {
+ vespalib::Gate gate;
+ virtual void execute() {
+ gate.countDown();
+ }
+};
+
+TEST("sync execute") {
+ DoIt exe1;
+ DoIt exe2;
+ DoIt exe3;
+ DoIt exe4;
+ FastOS_ThreadPool pool(128 * 1024 * 1024);
+ FNET_Transport transport;
+ ASSERT_TRUE(transport.execute(&exe1));
+ ASSERT_TRUE(transport.Start(&pool));
+ exe1.gate.await();
+ ASSERT_TRUE(transport.execute(&exe2));
+ transport.sync();
+ ASSERT_TRUE(exe2.gate.getCount() == 0u);
+ ASSERT_TRUE(transport.execute(&exe3));
+ transport.ShutDown(false);
+ ASSERT_TRUE(!transport.execute(&exe4));
+ transport.sync();
+ transport.WaitFinished();
+ transport.sync();
+ pool.Close();
+ ASSERT_TRUE(exe1.gate.getCount() == 0u);
+ ASSERT_TRUE(exe2.gate.getCount() == 0u);
+ ASSERT_TRUE(exe3.gate.getCount() == 0u);
+ ASSERT_TRUE(exe4.gate.getCount() == 1u);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/thread_selection/.gitignore b/fnet/src/tests/thread_selection/.gitignore
new file mode 100644
index 00000000000..e0d5b3c18f7
--- /dev/null
+++ b/fnet/src/tests/thread_selection/.gitignore
@@ -0,0 +1 @@
+fnet_thread_selection_test_app
diff --git a/fnet/src/tests/thread_selection/CMakeLists.txt b/fnet/src/tests/thread_selection/CMakeLists.txt
new file mode 100644
index 00000000000..183781f3b21
--- /dev/null
+++ b/fnet/src/tests/thread_selection/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_thread_selection_test_app
+ SOURCES
+ thread_selection_test.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_thread_selection_test_app COMMAND fnet_thread_selection_test_app)
diff --git a/fnet/src/tests/thread_selection/thread_selection_test.cpp b/fnet/src/tests/thread_selection/thread_selection_test.cpp
new file mode 100644
index 00000000000..5e5f0891ba4
--- /dev/null
+++ b/fnet/src/tests/thread_selection/thread_selection_test.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+#include <thread>
+#include <chrono>
+#include <mutex>
+#include <map>
+
+struct Fixture {
+ std::mutex lock;
+ FNET_Transport transport;
+ std::map<FNET_TransportThread *, size_t> counts;
+ Fixture(size_t num_threads) : transport(num_threads) {}
+ void count_selected_thread(const void *key, size_t key_len) {
+ std::lock_guard<std::mutex> guard(lock);
+ FNET_TransportThread *thread = transport.select_thread(key, key_len);
+ ++counts[thread];
+ }
+ std::vector<size_t> get_counts() {
+ std::vector<size_t> result;
+ for (const auto &entry: counts) {
+ result.push_back(entry.second);
+ }
+ return result;
+ }
+ void dump_counts() {
+ std::vector<size_t> list = get_counts();
+ fprintf(stderr, "thread selection counts: [");
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (i > 0) {
+ fprintf(stderr, ", ");
+ }
+ fprintf(stderr, "%zu", list[i]);
+ }
+ fprintf(stderr, "]\n");
+ }
+};
+
+TEST_F("require that selection is time sensistive", Fixture(8))
+{
+ using namespace std::literals;
+ vespalib::string key("my random key");
+ for (size_t i = 0; i < 256; ++i) {
+ f1.count_selected_thread(key.data(), key.size());
+ std::this_thread::sleep_for(10ms);
+ }
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_F("require that selection is key sensistive", Fixture(8))
+{
+ for (size_t i = 0; i < 256; ++i) {
+ vespalib::string key = vespalib::make_string("my random key %zu", i);
+ f1.count_selected_thread(key.data(), key.size());
+ }
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_MT_F("require that selection is thread sensitive", 256, Fixture(8))
+{
+ f1.count_selected_thread(nullptr, 0);
+ TEST_BARRIER();
+ if (thread_id == 0) {
+ std::vector<size_t> counts = f1.get_counts();
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+ }
+}
+
+void recursive_select(Fixture &f, size_t n) {
+ char dummy[32];
+ if (n > 0) {
+ recursive_select(f, n - 1);
+ f.count_selected_thread(nullptr, 0);
+ (void) dummy;
+ }
+}
+
+TEST_F("require that selection is stack location sensistive", Fixture(8))
+{
+ recursive_select(f, 256);
+ EXPECT_EQUAL(f1.counts.size(), 8u);
+ f1.dump_counts();
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/tests/time/.gitignore b/fnet/src/tests/time/.gitignore
new file mode 100644
index 00000000000..fddb94a8e8a
--- /dev/null
+++ b/fnet/src/tests/time/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+timespeed_test
+fnet_timespeed_test_app
diff --git a/fnet/src/tests/time/CMakeLists.txt b/fnet/src/tests/time/CMakeLists.txt
new file mode 100644
index 00000000000..5f620af2b53
--- /dev/null
+++ b/fnet/src/tests/time/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(fnet_timespeed_test_app
+ SOURCES
+ timespeed.cpp
+ DEPENDS
+ fnet
+)
+vespa_add_test(NAME fnet_timespeed_test_app NO_VALGRIND COMMAND fnet_timespeed_test_app)
diff --git a/fnet/src/tests/time/DESC b/fnet/src/tests/time/DESC
new file mode 100644
index 00000000000..27214fa9bcc
--- /dev/null
+++ b/fnet/src/tests/time/DESC
@@ -0,0 +1 @@
+Check how fast we can determine what time it is.
diff --git a/fnet/src/tests/time/FILES b/fnet/src/tests/time/FILES
new file mode 100644
index 00000000000..bf3708f36fa
--- /dev/null
+++ b/fnet/src/tests/time/FILES
@@ -0,0 +1 @@
+timespeed.cpp
diff --git a/fnet/src/tests/time/timespeed.cpp b/fnet/src/tests/time/timespeed.cpp
new file mode 100644
index 00000000000..cdbaaa27781
--- /dev/null
+++ b/fnet/src/tests/time/timespeed.cpp
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/fnet/fnet.h>
+
+
+class DummyTime
+{
+public:
+ void SetNow() {}
+};
+
+
+TEST("time speed") {
+ FastOS_Time start;
+ FastOS_Time stop;
+ DummyTime dummy;
+ FastOS_Time now;
+ double dummyTime;
+ double actualTime;
+ double overhead;
+ uint32_t i;
+
+ start.SetNow();
+ for (i = 0; i < 100000; i++) {
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ dummy.SetNow();
+ }
+ stop.SetNow();
+ stop -= start;
+ dummyTime = stop.MilliSecs();
+
+ fprintf(stderr, "1M dummy SetNow: %f ms (%1.2f/ms)\n",
+ dummyTime, 1000000.0 / dummyTime);
+
+ start.SetNow();
+ for (i = 0; i < 100000; i++) {
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ now.SetNow();
+ }
+ stop.SetNow();
+ stop -= start;
+ actualTime = stop.MilliSecs();
+
+ fprintf(stderr, "1M actual SetNow: %f ms (%1.2f/ms)\n",
+ stop.MilliSecs(), 1000000.0 / stop.MilliSecs());
+
+ overhead = (actualTime - dummyTime) / 1000.0;
+
+ fprintf(stderr, "approx overhead per SetNow: %f microseconds\n", overhead);
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/fnet/src/vespa/fnet/.gitignore b/fnet/src/vespa/fnet/.gitignore
new file mode 100644
index 00000000000..8463046a2c0
--- /dev/null
+++ b/fnet/src/vespa/fnet/.gitignore
@@ -0,0 +1,6 @@
+*.So
+.depend
+Makefile
+features.h
+fnet.lib
+/libfnet.so.5.1
diff --git a/fnet/src/vespa/fnet/CMakeLists.txt b/fnet/src/vespa/fnet/CMakeLists.txt
new file mode 100644
index 00000000000..2df853b2893
--- /dev/null
+++ b/fnet/src/vespa/fnet/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(fnet
+ SOURCES
+ channel.cpp
+ channellookup.cpp
+ config.cpp
+ connect_thread.cpp
+ connection.cpp
+ connector.cpp
+ controlpacket.cpp
+ databuffer.cpp
+ dummypacket.cpp
+ fdselector.cpp
+ info.cpp
+ iocomponent.cpp
+ packet.cpp
+ packetqueue.cpp
+ scheduler.cpp
+ signalshutdown.cpp
+ simplepacketstreamer.cpp
+ stats.cpp
+ task.cpp
+ transport.cpp
+ transport_thread.cpp
+ vtag.cpp
+ $<TARGET_OBJECTS:fnet_frt>
+ INSTALL lib64
+ DEPENDS
+)
diff --git a/fnet/src/vespa/fnet/channel.cpp b/fnet/src/vespa/fnet/channel.cpp
new file mode 100644
index 00000000000..7d6de1893bc
--- /dev/null
+++ b/fnet/src/vespa/fnet/channel.cpp
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+bool
+FNET_Channel::Send(FNET_Packet *packet)
+{
+ return _conn->PostPacket(packet, _id);
+}
+
+void
+FNET_Channel::Sync()
+{
+ _conn->Sync();
+}
+
+FNET_IPacketHandler::HP_RetCode
+FNET_Channel::Receive(FNET_Packet *packet)
+{
+ return _handler->HandlePacket(packet, _context);
+}
+
+bool
+FNET_Channel::Close()
+{
+ return _conn->CloseChannel(this);
+}
+
+void
+FNET_Channel::Free()
+{
+ _conn->FreeChannel(this);
+}
+
+void
+FNET_Channel::CloseAndFree()
+{
+ _conn->CloseAndFreeChannel(this);
+}
diff --git a/fnet/src/vespa/fnet/channel.h b/fnet/src/vespa/fnet/channel.h
new file mode 100644
index 00000000000..ee8ad42ff90
--- /dev/null
+++ b/fnet/src/vespa/fnet/channel.h
@@ -0,0 +1,95 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * A channel object represents an endpoint in a point-to-point packet
+ * based virtual connection. Clients open channels by invoking the
+ * OpenChannel method on objects of the ServerInfo class. Servers need
+ * to listen for incoming channels by implementing the ServerAdapter
+ * interface.
+ **/
+class FNET_Channel
+{
+private:
+ uint32_t _id;
+ FNET_Connection *_conn;
+ FNET_IPacketHandler *_handler;
+ FNET_Context _context;
+
+ FNET_Channel(const FNET_Channel &);
+ FNET_Channel &operator=(const FNET_Channel &);
+
+public:
+ typedef std::unique_ptr<FNET_Channel> UP;
+
+ FNET_Channel(uint32_t id = FNET_NOID, FNET_Connection * conn = nullptr, FNET_IPacketHandler * handler = nullptr, FNET_Context context = FNET_Context())
+ : _id(id), _conn(conn), _handler(handler), _context(context)
+ { }
+ void SetID(uint32_t id) { _id = id; }
+ void SetConnection(FNET_Connection *conn) { _conn = conn; }
+ void SetHandler(FNET_IPacketHandler *handler) { _handler = handler; }
+ void SetContext(FNET_Context context) { _context = context; }
+ void prefetch() {
+ __builtin_prefetch(&_handler, 0);
+ }
+
+ uint32_t GetID() { return _id; }
+ FNET_Connection *GetConnection() { return _conn; }
+ FNET_IPacketHandler *GetHandler() { return _handler; }
+ FNET_Context GetContext() { return _context; }
+
+ /**
+ * Send a packet on this channel. This operation will fail if the
+ * underlying connection is closed. NOTE: packet handover (caller TO
+ * invoked object).
+ *
+ * @return false if the connection is down, true otherwise.
+ * @param packet the packet to send.
+ **/
+ bool Send(FNET_Packet *packet);
+
+ /**
+ * Sync with the underlying connection. When this method is invoked
+ * it will block until all packets currently posted on the
+ * underlying connection is encoded into the output buffer. Also,
+ * the amount of data in the connection output buffer will be less
+ * than the amount given by FNET_Connection::FNET_WRITE_SIZE.
+ **/
+ void Sync();
+
+ /**
+ * This method is called when a packet was received on this channel.
+ * NOTE: packet handover (caller TO invoked object).
+ *
+ * @return channel command: keep open, close or free.
+ * @param packet the received packet.
+ **/
+ FNET_IPacketHandler::HP_RetCode
+ Receive(FNET_Packet *packet);
+
+ /**
+ * Close this channel. After a channel is closed, no more packets
+ * will be delivered through the channel by the network layer. The
+ * application may however still send packets on the channel.
+ *
+ * @return false if the channel was not open.
+ **/
+ bool Close();
+
+ /**
+ * Free this channel. This will free the connection reference owned
+ * by this channel and also put the channel object back on the
+ * object pool for re-use. This channel object may not be used after
+ * this method is called.
+ **/
+ void Free();
+
+ /**
+ * Close and Free this channel in a single operation. This has the
+ * same effect as invoking @ref Close then @ref Free, but is more
+ * efficient.
+ **/
+ void CloseAndFree();
+};
+
diff --git a/fnet/src/vespa/fnet/channellookup.cpp b/fnet/src/vespa/fnet/channellookup.cpp
new file mode 100644
index 00000000000..df05b691bc1
--- /dev/null
+++ b/fnet/src/vespa/fnet/channellookup.cpp
@@ -0,0 +1,66 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+FNET_ChannelLookup::FNET_ChannelLookup(uint32_t hashSize)
+ : _map(hashSize)
+{
+ assert(hashSize > 0);
+}
+
+
+FNET_ChannelLookup::~FNET_ChannelLookup()
+{
+ assert(_map.empty());
+}
+
+
+void
+FNET_ChannelLookup::Register(FNET_Channel *channel)
+{
+ assert(channel->GetHandler() != nullptr);
+ _map[channel->GetID()] = channel;
+}
+
+
+FNET_Channel*
+FNET_ChannelLookup::Lookup(uint32_t id)
+{
+ auto found = _map.find(id);
+ return ((found != _map.end()) ? found->second : nullptr);
+}
+
+
+std::vector<FNET_Channel::UP>
+FNET_ChannelLookup::Broadcast(FNET_ControlPacket *cpacket)
+{
+ std::vector<uint32_t> toRemove;
+ std::vector<FNET_Channel::UP> toFree;
+ for (const auto & pair : _map) {
+ FNET_Channel *ch = pair.second;
+ FNET_IPacketHandler::HP_RetCode hp_rc = ch->Receive(cpacket);
+ if (hp_rc > FNET_IPacketHandler::FNET_KEEP_CHANNEL) {
+ toRemove.push_back(pair.first);
+ if (hp_rc == FNET_IPacketHandler::FNET_FREE_CHANNEL) {
+ toFree.emplace_back(ch);
+ }
+ }
+ }
+ for (uint32_t id : toRemove) {
+ _map.erase(id);
+ }
+ return toFree;
+}
+
+bool
+FNET_ChannelLookup::Unregister(FNET_Channel *channel)
+{
+ auto found = _map.find(channel->GetID());
+ if (found == _map.end()) {
+ return false;
+ }
+ _map.erase(found);
+ return true;
+}
diff --git a/fnet/src/vespa/fnet/channellookup.h b/fnet/src/vespa/fnet/channellookup.h
new file mode 100644
index 00000000000..df694c0fa61
--- /dev/null
+++ b/fnet/src/vespa/fnet/channellookup.h
@@ -0,0 +1,91 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/stllike/hash_map.h>
+
+/**
+ * This class handles registration/deregistration and lookup of
+ * Channel objects. Note that locking must be done by the users of
+ * this class in order to obtain thread safety. This class is used by
+ * the @ref FNET_Connection class to keep track of channels. NOTE:
+ * this class is not intended for direct use; use at own risk.
+ **/
+class FNET_ChannelLookup
+{
+private:
+ typedef vespalib::hash_map<uint32_t, FNET_Channel *> ChannelMap;
+ ChannelMap _map;
+
+ FNET_ChannelLookup(const FNET_ChannelLookup &);
+ FNET_ChannelLookup &operator=(const FNET_ChannelLookup &);
+
+public:
+ /**
+ * Construct a channel lookup.
+ *
+ * @param hashSize Size of hash table used for lookup. Currently the
+ * hash size may not be modified during the lifetime of a
+ * ChannelLookup object.
+ **/
+ FNET_ChannelLookup(uint32_t hashSize = 16);
+
+ /**
+ * Delete hash table. NOTE: The hash table must be empty when this
+ * method is called.
+ **/
+ ~FNET_ChannelLookup();
+
+ /**
+ * @return number of registered channels.
+ **/
+ uint32_t GetEntryCnt() { return _map.size(); }
+
+ /**
+ * Register a channel. If you register several channels with the
+ * same ID, only the last registered channel will be availible by
+ * calling the Lookup method.
+ *
+ * @param channel the channel you want to register.
+ **/
+ void Register(FNET_Channel *channel);
+
+ /**
+ * Find a channel given the channel ID.
+ *
+ * @return channel with correct id or nullptr if not found.
+ * @param id channel ID of the channel you are looking for.
+ **/
+ FNET_Channel *Lookup(uint32_t id);
+
+ /**
+ * Broadcast a control packet to all channels registered with this
+ * channel lookup. The given control packet will be sent on all
+ * channels currently registered. The control packet is sent
+ * sequentially (using the calling thread) to all channels in no
+ * special order. When receiving a packet, the packet handler
+ * registered for a channel may use the return code of the
+ * HandlePacket method to indicate that the channel should be kept
+ * open, closed or even freed. Keeping channels open and closing
+ * them (unregistering them) is performed internally by this
+ * method. Freeing channels, however, is left to the caller of this
+ * method. The channels that are to be freed are linked together and
+ * returned from this method.
+ *
+ * @return vector of all channels to be freed.
+ * @param cpacket the control packet you want to broadcast.
+ **/
+ std::vector<FNET_Channel::UP> Broadcast(FNET_ControlPacket *cpacket);
+
+ /**
+ * Unregister a channel. This method uses both the channel ID and
+ * the object identity of the parameter to ensure that the correct
+ * object is unregistered even if there are several channels
+ * currently registered with the same channel ID.
+ *
+ * @return true(ok)/false(not found)
+ * @param channel the channel you want to unregister.
+ **/
+ bool Unregister(FNET_Channel *channel);
+};
+
diff --git a/fnet/src/vespa/fnet/config.cpp b/fnet/src/vespa/fnet/config.cpp
new file mode 100644
index 00000000000..18fe5fd04b7
--- /dev/null
+++ b/fnet/src/vespa/fnet/config.cpp
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+FNET_Config::FNET_Config()
+ : _minEventTimeOut(0),
+ _pingInterval(0),
+ _iocTimeOut(0),
+ _maxInputBufferSize(0x10000),
+ _maxOutputBufferSize(0x10000),
+ _tcpNoDelay(true),
+ _logStats(false),
+ _directWrite(true)
+{
+}
+
+
+FNET_Config::~FNET_Config()
+{
+}
diff --git a/fnet/src/vespa/fnet/config.h b/fnet/src/vespa/fnet/config.h
new file mode 100644
index 00000000000..54c6c618532
--- /dev/null
+++ b/fnet/src/vespa/fnet/config.h
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class is used internally by the @ref FNET_Transport to keep
+ * track of the current configuration.
+ **/
+class FNET_Config
+{
+public:
+ uint32_t _minEventTimeOut;
+ uint32_t _pingInterval;
+ uint32_t _iocTimeOut;
+ uint32_t _maxInputBufferSize;
+ uint32_t _maxOutputBufferSize;
+ bool _tcpNoDelay;
+ bool _logStats;
+ bool _directWrite;
+
+ FNET_Config();
+ ~FNET_Config();
+};
+
diff --git a/fnet/src/vespa/fnet/connect_thread.cpp b/fnet/src/vespa/fnet/connect_thread.cpp
new file mode 100644
index 00000000000..0e74a853886
--- /dev/null
+++ b/fnet/src/vespa/fnet/connect_thread.cpp
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+#include <vespa/fastos/fastos.h>
+#include "connect_thread.h"
+
+namespace fnet {
+
+void
+ConnectThread::run()
+{
+ for (;;) {
+ Guard guard(_lock);
+ while (!_done && _queue.empty()) {
+ _cond.wait(guard);
+ }
+ if (_done && _queue.empty()) {
+ return;
+ }
+ assert(!_queue.empty());
+ ExtConnectable *conn = _queue.front();
+ _queue.pop();
+ guard.unlock(); // UNLOCK
+ conn->ext_connect();
+ }
+}
+
+ConnectThread::ConnectThread()
+ : _lock(),
+ _cond(),
+ _queue(),
+ _done(false),
+ _thread(&ConnectThread::run, this)
+{
+}
+
+ConnectThread::~ConnectThread()
+{
+ {
+ Guard guard(_lock);
+ _done = true;
+ _cond.notify_one();
+ }
+ _thread.join();
+ assert(_queue.empty());
+}
+
+void
+ConnectThread::connect_later(ExtConnectable *conn)
+{
+ Guard guard(_lock);
+ assert(!_done);
+ _queue.push(conn);
+ _cond.notify_one();
+}
+
+} // namespace fnet
diff --git a/fnet/src/vespa/fnet/connect_thread.h b/fnet/src/vespa/fnet/connect_thread.h
new file mode 100644
index 00000000000..9782fa50a75
--- /dev/null
+++ b/fnet/src/vespa/fnet/connect_thread.h
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+
+#pragma once
+
+#include <vespa/vespalib/util/arrayqueue.hpp>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+namespace fnet {
+
+/**
+ * Interface implemented by objects that want to perform synchronous
+ * connect initiated by an external thread.
+ **/
+class ExtConnectable {
+protected:
+ virtual ~ExtConnectable() {}
+public:
+ virtual void ext_connect() = 0;
+};
+
+/**
+ * An object encapsulating a thread responsible for doing synchronous
+ * external connect.
+ **/
+class ConnectThread
+{
+private:
+ using Guard = std::unique_lock<std::mutex>;
+
+ std::mutex _lock;
+ std::condition_variable _cond;
+ vespalib::ArrayQueue<ExtConnectable*> _queue;
+ bool _done;
+ std::thread _thread;
+
+ void run();
+
+public:
+ ConnectThread();
+ ~ConnectThread();
+ void connect_later(ExtConnectable *conn);
+};
+
+} // namespace fnet
diff --git a/fnet/src/vespa/fnet/connection.cpp b/fnet/src/vespa/fnet/connection.cpp
new file mode 100644
index 00000000000..259702072cf
--- /dev/null
+++ b/fnet/src/vespa/fnet/connection.cpp
@@ -0,0 +1,699 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+
+void
+FNET_Connection::SyncPacket::Free()
+{
+ _cond.Lock();
+ _done = true;
+ if (_waiting)
+ _cond.Signal();
+ _cond.Unlock();
+}
+
+///////////////////////
+// PROTECTED METHODS //
+///////////////////////
+
+const char*
+FNET_Connection::GetStateString(State state)
+{
+ switch(state) {
+ case FNET_CONNECTING:
+ return "CONNECTING";
+ case FNET_CONNECTED:
+ return "CONNECTED";
+ case FNET_CLOSING:
+ return "CLOSING";
+ case FNET_CLOSED:
+ return "CLOSED";
+ default:
+ return "ILLEGAL";
+ }
+}
+
+
+void
+FNET_Connection::SetState(State state)
+{
+ State oldstate;
+
+ std::vector<FNET_Channel::UP> toDelete;
+ Lock();
+ oldstate = _state;
+ _state = state;
+ if (LOG_WOULD_LOG(debug) && state != oldstate) {
+ LOG(debug, "Connection(%s): State transition: %s -> %s", GetSpec(),
+ GetStateString(oldstate), GetStateString(state));
+ }
+ if (oldstate < FNET_CLOSING && state >= FNET_CLOSING) {
+
+ if (_flags._writeLock) {
+ _flags._discarding = true;
+ while (_flags._writeLock)
+ Wait();
+ _flags._discarding = false;
+ }
+
+ while (!_queue.IsEmpty_NoLock() || !_myQueue.IsEmpty_NoLock()) {
+ _flags._discarding = true;
+ _queue.FlushPackets_NoLock(&_myQueue);
+ Unlock();
+ _myQueue.DiscardPackets_NoLock();
+ Lock();
+ _flags._discarding = false;
+ }
+
+ BeforeCallback(NULL);
+ toDelete = _channels.Broadcast(&FNET_ControlPacket::ChannelLost);
+ AfterCallback();
+ }
+
+ if ( ! toDelete.empty() ) {
+ FNET_Channel *ach = _adminChannel;
+
+ for (const FNET_Channel::UP & ch : toDelete) {
+ if (ch.get() == ach) {
+ _adminChannel = NULL;
+ } else {
+ SubRef_NoLock();
+ }
+ }
+ }
+ Unlock();
+}
+
+
+void
+FNET_Connection::HandlePacket(uint32_t plen, uint32_t pcode,
+ uint32_t chid)
+{
+ FNET_Packet *packet;
+ FNET_Channel *channel;
+ FNET_IPacketHandler::HP_RetCode hp_rc;
+
+ Lock();
+ channel = _channels.Lookup(chid);
+
+ if (channel != NULL) { // deliver packet on open channel
+ channel->prefetch(); // Prefetch in the shadow of the lock operation in BeforeCallback.
+ __builtin_prefetch(&_streamer);
+ __builtin_prefetch(&_input);
+
+ BeforeCallback(channel);
+ __builtin_prefetch(channel->GetHandler(), 0); // Prefetch the handler while packet is being decoded.
+ packet = _streamer->Decode(&_input, plen, pcode, channel->GetContext());
+ hp_rc = (packet != NULL) ? channel->Receive(packet)
+ : channel->Receive(&FNET_ControlPacket::BadPacket);
+ AfterCallback();
+
+ FNET_Channel::UP toDelete;
+ if (hp_rc > FNET_IPacketHandler::FNET_KEEP_CHANNEL) {
+ _channels.Unregister(channel);
+
+ if (hp_rc == FNET_IPacketHandler::FNET_FREE_CHANNEL) {
+ if (channel == _adminChannel) {
+ _adminChannel = NULL;
+ } else {
+ SubRef_NoLock();
+ }
+ toDelete.reset(channel);
+ }
+ }
+
+ Unlock();
+
+ } else if (CanAcceptChannels() && IsFromPeer(chid)) { // open new channel
+ FNET_Channel::UP newChannel(new FNET_Channel(chid, this));
+ channel = newChannel.get();
+ AddRef_NoLock();
+ BeforeCallback(channel);
+
+ if (_serverAdapter->InitChannel(channel, pcode)) {
+
+ packet = _streamer->Decode(&_input, plen, pcode, channel->GetContext());
+ hp_rc = (packet != NULL) ? channel->Receive(packet)
+ : channel->Receive(&FNET_ControlPacket::BadPacket);
+ AfterCallback();
+
+ if (hp_rc == FNET_IPacketHandler::FNET_FREE_CHANNEL) {
+ SubRef_NoLock();
+ } else if (hp_rc == FNET_IPacketHandler::FNET_KEEP_CHANNEL) {
+ _channels.Register(newChannel.release());
+ } else {
+ newChannel.release(); // It has already been taken care of, so we should not free it here.
+ }
+
+ Unlock();
+
+ } else {
+
+ AfterCallback();
+ SubRef_NoLock();
+ Unlock();
+
+ LOG(debug, "Connection(%s): channel init failed", GetSpec());
+ _input.DataToDead(plen);
+ }
+
+ } else { // skip unhandled packet
+
+ Unlock();
+ LOG(spam, "Connection(%s): skipping unhandled packet", GetSpec());
+ _input.DataToDead(plen);
+ }
+}
+
+
+bool
+FNET_Connection::Read()
+{
+ uint32_t readData = 0; // total data read
+ uint32_t readPackets = 0; // total packets read
+ int readCnt = 0; // read count
+ bool broken = false; // is this conn broken ?
+ int error; // socket error code
+ ssize_t res; // single read result
+
+ _input.EnsureFree(FNET_READ_SIZE);
+ res = _socket->Read(_input.GetFree(), _input.GetFreeLen());
+ readCnt++;
+
+ while (res > 0) {
+ _input.FreeToData((uint32_t)res);
+ readData += (uint32_t)res;
+
+ for (;;) { // handle each complete packet in the buffer.
+
+ if (!_flags._gotheader)
+ _flags._gotheader = _streamer->GetPacketInfo(&_input, &_packetLength,
+ &_packetCode, &_packetCHID,
+ &broken);
+
+ if (_flags._gotheader && _input.GetDataLen() >= _packetLength) {
+ readPackets++;
+ HandlePacket(_packetLength, _packetCode, _packetCHID);
+ _flags._gotheader = false; // reset header flag.
+ } else {
+ if (broken)
+ goto done_read;
+ break;
+ }
+ }
+ _input.resetIfEmpty();
+
+ if (_input.GetFreeLen() > 0
+ || readCnt >= FNET_READ_REDO) // prevent starvation
+ goto done_read;
+
+ _input.EnsureFree(FNET_READ_SIZE);
+ res = _socket->Read(_input.GetFree(), _input.GetFreeLen());
+ readCnt++;
+ }
+
+done_read:
+
+ if (readData > 0) {
+ UpdateTimeOut();
+ CountDataRead(readData);
+ CountPacketRead(readPackets);
+ uint32_t maxSize = GetConfig()->_maxInputBufferSize;
+ if (maxSize > 0 && _input.GetBufSize() > maxSize)
+ {
+ if (!_flags._gotheader || _packetLength < maxSize) {
+ _input.Shrink(maxSize);
+ }
+ }
+ }
+
+ if (res <= 0) {
+ if (res == 0) {
+ broken = true; // handle EOF
+ } else { // res < 0
+ error = FastOS_Socket::GetLastError();
+ broken = (error != FastOS_Socket::ERR_WOULDBLOCK &&
+ error != FastOS_Socket::ERR_AGAIN);
+
+ if (broken && error != FastOS_Socket::ERR_CONNRESET) {
+
+ LOG(debug, "Connection(%s): read error: %d", GetSpec(), error);
+ }
+ }
+ }
+
+ return !broken;
+}
+
+
+bool
+FNET_Connection::Write(bool direct)
+{
+ uint32_t writtenData = 0; // total data written
+ uint32_t writtenPackets = 0; // total packets written
+ int writeCnt = 0; // write count
+ bool broken = false; // is this conn broken ?
+ int error = 0; // no error (yet)
+ ssize_t res; // single write result
+
+ FNET_Packet *packet;
+ FNET_Context context;
+
+ do {
+
+ // fill output buffer
+
+ while (_output.GetDataLen() < FNET_WRITE_SIZE) {
+ if (_myQueue.IsEmpty_NoLock())
+ break;
+
+ packet = _myQueue.DequeuePacket_NoLock(&context);
+ if (packet->IsRegularPacket()) { // ignore non-regular packets
+ _streamer->Encode(packet, context._value.INT, &_output);
+ writtenPackets++;
+ }
+ packet->Free();
+ }
+
+ if (_output.GetDataLen() == 0) {
+ res = 0;
+ break;
+ }
+
+ // write data
+
+ res = _socket->Write(_output.GetData(), _output.GetDataLen());
+ writeCnt++;
+ if (res > 0) {
+ _output.DataToDead((uint32_t)res);
+ writtenData += (uint32_t)res;
+ _output.resetIfEmpty();
+ }
+ } while (res > 0 &&
+ _output.GetDataLen() == 0 &&
+ !_myQueue.IsEmpty_NoLock() &&
+ writeCnt < FNET_WRITE_REDO);
+
+ if (writtenData > 0) {
+ uint32_t maxSize = GetConfig()->_maxOutputBufferSize;
+ if (maxSize > 0 && _output.GetBufSize() > maxSize) {
+ _output.Shrink(maxSize);
+ }
+ }
+
+ if (res < 0) {
+ error = FastOS_Socket::GetLastError();
+ broken = (error != FastOS_Socket::ERR_WOULDBLOCK &&
+ error != FastOS_Socket::ERR_AGAIN);
+
+ if (broken) {
+ LOG(debug, "Connection(%s): write error: %d", GetSpec(), error);
+ }
+ }
+
+ Lock();
+ _writeWork = _queue.GetPacketCnt_NoLock()
+ + _myQueue.GetPacketCnt_NoLock()
+ + ((_output.GetDataLen() > 0) ? 1 : 0);
+ _flags._writeLock = false;
+ if (_flags._discarding)
+ Broadcast();
+ bool writePending = (_writeWork > 0);
+
+ if (direct) { // direct write (from post packet)
+ if (writtenData > 0) {
+ CountDirectDataWrite(writtenData);
+ CountDirectPacketWrite(writtenPackets);
+ }
+ if (writePending) {
+ AddRef_NoLock();
+ Unlock();
+ if (broken) {
+ Owner()->Close(this, /* needRef = */ false);
+ } else {
+ Owner()->EnableWrite(this, /* needRef = */ false);
+ }
+ } else {
+ Unlock();
+ }
+ } else { // normal write (from event loop)
+ Unlock();
+ if (writtenData > 0) {
+ CountDataWrite(writtenData);
+ CountPacketWrite(writtenPackets);
+ }
+ if (!writePending)
+ EnableWriteEvent(false);
+ }
+
+ return !broken;
+}
+
+////////////////////
+// PUBLIC METHODS //
+////////////////////
+
+
+FNET_Connection::FNET_Connection(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ FastOS_Socket *mySocket,
+ const char *spec)
+ : FNET_IOComponent(owner, mySocket, spec, /* time-out = */ true),
+ _streamer(streamer),
+ _serverAdapter(serverAdapter),
+ _adminChannel(NULL),
+ _socket(mySocket),
+ _context(),
+ _state(FNET_CONNECTED), // <-- NB
+ _flags(),
+ _packetLength(0),
+ _packetCode(0),
+ _packetCHID(0),
+ _writeWork(0),
+ _currentID(1), // <-- NB
+ _input(FNET_READ_SIZE * 2),
+ _queue(256),
+ _myQueue(256),
+ _output(FNET_WRITE_SIZE * 2),
+ _channels(),
+ _callbackTarget(NULL),
+ _cleanup(NULL)
+{
+ assert(_socket != NULL);
+ LOG(debug, "Connection(%s): State transition: %s -> %s", GetSpec(),
+ GetStateString(FNET_CONNECTING), GetStateString(FNET_CONNECTED));
+}
+
+
+FNET_Connection::FNET_Connection(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ FNET_IPacketHandler *adminHandler,
+ FNET_Context adminContext,
+ FNET_Context context,
+ FastOS_Socket *mySocket,
+ const char *spec)
+ : FNET_IOComponent(owner, mySocket, spec, /* time-out = */ true),
+ _streamer(streamer),
+ _serverAdapter(serverAdapter),
+ _adminChannel(NULL),
+ _socket(mySocket),
+ _context(context),
+ _state(FNET_CONNECTING),
+ _flags(),
+ _packetLength(0),
+ _packetCode(0),
+ _packetCHID(0),
+ _writeWork(0),
+ _currentID(0),
+ _input(FNET_READ_SIZE * 2),
+ _queue(256),
+ _myQueue(256),
+ _output(FNET_WRITE_SIZE * 2),
+ _channels(),
+ _callbackTarget(NULL),
+ _cleanup(NULL)
+{
+ assert(_socket != NULL);
+ if (adminHandler != NULL) {
+ FNET_Channel::UP admin(new FNET_Channel(FNET_NOID, this, adminHandler, adminContext));
+ _adminChannel = admin.get();
+ _channels.Register(admin.release());
+ }
+}
+
+
+FNET_Connection::~FNET_Connection()
+{
+ if (_adminChannel != NULL) {
+ _channels.Unregister(_adminChannel);
+ delete _adminChannel;
+ }
+ assert(_cleanup == NULL);
+ assert(_socket->GetSocketEvent() == NULL);
+ assert(!_flags._writeLock);
+ delete _socket;
+}
+
+
+bool
+FNET_Connection::Init()
+{
+ bool rc = _socket->TuneTransport();
+ if (rc) {
+ if (GetConfig()->_tcpNoDelay)
+ _socket->SetNoDelay(true);
+ EnableReadEvent(true);
+ EnableWriteEvent(true);
+ }
+
+ // init server admin channel
+ if (rc && CanAcceptChannels() && _adminChannel == NULL) {
+ FNET_Channel::UP ach(new FNET_Channel(FNET_NOID, this));
+ if (_serverAdapter->InitAdminChannel(ach.get())) {
+ AddRef_NoLock();
+ _channels.Register(ach.release());
+ }
+ }
+
+ // handle close by admin channel init
+ return (rc && _state <= FNET_CONNECTED);
+}
+
+void
+FNET_Connection::ext_connect()
+{
+ bool rc = _socket->Connect() // NB: sync connect
+ && _socket->SetSoBlocking(false);
+ if (rc) {
+ SetState(FNET_CONNECTED);
+ Owner()->Add(this, /* needRef = */ false);
+ } else {
+ Owner()->Add(this, /* needRef = */ true);
+ Owner()->Close(this, /* needRef = */ false);
+ }
+}
+
+void
+FNET_Connection::SetCleanupHandler(FNET_IConnectionCleanupHandler *handler)
+{
+ _cleanup = handler;
+}
+
+
+FNET_Channel*
+FNET_Connection::OpenChannel(FNET_IPacketHandler *handler,
+ FNET_Context context,
+ uint32_t *chid)
+{
+ FNET_Channel::UP newChannel(new FNET_Channel(FNET_NOID, this, handler, context));
+ FNET_Channel * ret = nullptr;
+
+ Lock();
+ if (__builtin_expect(_state < FNET_CLOSING, true)) {
+ newChannel->SetID(GetNextID());
+ if (chid != NULL) {
+ *chid = newChannel->GetID();
+ }
+ WaitCallback(NULL);
+ AddRef_NoLock();
+ ret = newChannel.release();
+ _channels.Register(ret);
+ }
+ Unlock();
+ return ret;
+}
+
+
+FNET_Channel*
+FNET_Connection::OpenChannel()
+{
+
+ Lock();
+ uint32_t chid = GetNextID();
+ AddRef_NoLock();
+ Unlock();
+ return new FNET_Channel(chid, this);
+}
+
+
+bool
+FNET_Connection::CloseChannel(FNET_Channel *channel)
+{
+ bool ret;
+ Lock();
+ WaitCallback(channel);
+ ret = _channels.Unregister(channel);
+ Unlock();
+ return ret;
+}
+
+
+void
+FNET_Connection::FreeChannel(FNET_Channel *channel)
+{
+ delete channel;
+ Lock();
+ SubRef_HasLock();
+}
+
+
+void
+FNET_Connection::CloseAndFreeChannel(FNET_Channel *channel)
+{
+ Lock();
+ WaitCallback(channel);
+ _channels.Unregister(channel);
+ SubRef_HasLock();
+ delete channel;
+}
+
+
+void
+FNET_Connection::CloseAdminChannel()
+{
+ Lock();
+ FNET_Channel::UP toDelete;
+ if (_adminChannel != NULL) {
+ WaitCallback(_adminChannel);
+ if (_adminChannel != NULL) {
+ _channels.Unregister(_adminChannel);
+ toDelete.reset(_adminChannel);
+ _adminChannel = NULL;
+ }
+ }
+ Unlock();
+}
+
+
+bool
+FNET_Connection::PostPacket(FNET_Packet *packet, uint32_t chid)
+{
+ uint32_t writeWork;
+
+ assert(packet != NULL);
+ Lock();
+ if (_state >= FNET_CLOSING) {
+ if (_flags._discarding) {
+ _queue.QueuePacket_NoLock(packet, FNET_Context(chid));
+ Unlock();
+ } else {
+ Unlock();
+ packet->Free(); // discard packet
+ }
+ return false; // connection is down
+ }
+ writeWork = _writeWork;
+ _writeWork++;
+ _queue.QueuePacket_NoLock(packet, FNET_Context(chid));
+ if (writeWork == 0 && !_flags._writeLock &&
+ _state == FNET_CONNECTED)
+ {
+ if (GetConfig()->_directWrite) {
+ _flags._writeLock = true;
+ _queue.FlushPackets_NoLock(&_myQueue);
+ Unlock();
+ Write(true);
+ } else {
+ AddRef_NoLock();
+ Unlock();
+ Owner()->EnableWrite(this, /* needRef = */ false);
+ }
+ } else {
+ Unlock();
+ }
+ return true;
+}
+
+
+uint32_t
+FNET_Connection::GetQueueLen()
+{
+ Lock();
+ uint32_t ret = _queue.GetPacketCnt_NoLock()
+ + _myQueue.GetPacketCnt_NoLock();
+ Unlock();
+ return ret;
+}
+
+
+void
+FNET_Connection::Sync()
+{
+ SyncPacket sp;
+ PostPacket(&sp, FNET_NOID);
+ sp.WaitFree();
+}
+
+
+void
+FNET_Connection::CleanupHook()
+{
+ if (_cleanup != NULL) {
+ _cleanup->Cleanup(this);
+ _cleanup = NULL;
+ }
+}
+
+
+void
+FNET_Connection::Close()
+{
+ SetSocketEvent(NULL);
+ SetState(FNET_CLOSED);
+ _socket->Shutdown();
+ _socket->Close();
+}
+
+
+bool
+FNET_Connection::HandleReadEvent()
+{
+ bool broken = false; // is connection broken ?
+
+ switch(_state) {
+ case FNET_CONNECTING: // ignore read events while connecting
+ break;
+ case FNET_CONNECTED:
+ broken = !Read();
+ break;
+ case FNET_CLOSING:
+ case FNET_CLOSED:
+ default:
+ broken = true;
+ }
+ return !broken;
+}
+
+
+bool
+FNET_Connection::HandleWriteEvent()
+{
+ bool broken = false; // is connection broken ?
+
+ switch(_state) {
+ case FNET_CONNECTING: // ignore write events while connecting
+ break;
+ case FNET_CONNECTED:
+ Lock();
+ if (_flags._writeLock) {
+ Unlock();
+ EnableWriteEvent(false);
+ return true;
+ }
+ _flags._writeLock = true;
+ _queue.FlushPackets_NoLock(&_myQueue);
+ Unlock();
+ broken = !Write(false);
+ break;
+ case FNET_CLOSING:
+ case FNET_CLOSED:
+ default:
+ broken = true;
+ }
+ return !broken;
+}
diff --git a/fnet/src/vespa/fnet/connection.h b/fnet/src/vespa/fnet/connection.h
new file mode 100644
index 00000000000..7b4ba82b90c
--- /dev/null
+++ b/fnet/src/vespa/fnet/connection.h
@@ -0,0 +1,509 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "connect_thread.h"
+
+/**
+ * Interface implemented by objects that want to perform connection
+ * cleanup. Use the SetCleanupHandler method to register with a
+ * connection. Currently, there can only be one cleanup handler per
+ * connection.
+ **/
+class FNET_IConnectionCleanupHandler
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FNET_IConnectionCleanupHandler(void) {}
+
+ /**
+ * Perform connection cleanup.
+ *
+ * @param conn the connection
+ **/
+ virtual void Cleanup(FNET_Connection *conn) = 0;
+};
+
+
+/**
+ * This class represents a single connection with another
+ * computer. The binary format on a connection is defined by the
+ * PacketStreamer given to the constructor. Each connection object may
+ * represent either the client or server end-point of a single TCP
+ * connection. Only the client side may open new channels on the
+ * connection.
+ **/
+class FNET_Connection : public FNET_IOComponent,
+ public fnet::ExtConnectable
+{
+public:
+ enum State {
+ FNET_CONNECTING,
+ FNET_CONNECTED,
+ FNET_CLOSING,
+ FNET_CLOSED
+ };
+
+ enum {
+ FNET_READ_SIZE = 8192,
+ FNET_READ_REDO = 10,
+ FNET_WRITE_SIZE = 8192,
+ FNET_WRITE_REDO = 10
+ };
+
+#ifndef IAM_DOXYGEN
+ class SyncPacket : public FNET_DummyPacket
+ {
+ private:
+ FNET_Cond _cond;
+ bool _done;
+ bool _waiting;
+
+ public:
+ SyncPacket()
+ : _cond(),
+ _done(false),
+ _waiting(false) {}
+ virtual ~SyncPacket() {}
+
+ void WaitFree()
+ {
+ _cond.Lock();
+ _waiting = true;
+ while(!_done)
+ _cond.Wait();
+ _waiting = false;
+ _cond.Unlock();
+ }
+
+ virtual void Free();
+ };
+#endif // DOXYGEN
+
+private:
+ struct Flags {
+ Flags() :
+ _gotheader(false),
+ _writeLock(false),
+ _inCallback(false),
+ _callbackWait(false),
+ _discarding(false)
+ { }
+ bool _gotheader;
+ bool _writeLock;
+ bool _inCallback;
+ bool _callbackWait;
+ bool _discarding;
+ };
+ FNET_IPacketStreamer *_streamer; // custom packet streamer
+ FNET_IServerAdapter *_serverAdapter; // only on server side
+ FNET_Channel *_adminChannel; // only on client side
+ FastOS_Socket *_socket; // socket for this conn
+ FNET_Context _context; // connection context
+ State _state; // connection state
+ Flags _flags; // Packed flags.
+ uint32_t _packetLength; // packet length
+ uint32_t _packetCode; // packet code
+ uint32_t _packetCHID; // packet chid
+ uint32_t _writeWork; // pending write work
+ uint32_t _currentID; // current channel ID
+ FNET_DataBuffer _input; // input buffer
+ FNET_PacketQueue_NoLock _queue; // outer output queue
+ FNET_PacketQueue_NoLock _myQueue; // inner output queue
+ FNET_DataBuffer _output; // output buffer
+ FNET_ChannelLookup _channels; // channel 'DB'
+ FNET_Channel *_callbackTarget; // target of current callback
+
+ FNET_IConnectionCleanupHandler *_cleanup; // cleanup handler
+
+ FNET_Connection(const FNET_Connection &);
+ FNET_Connection &operator=(const FNET_Connection &);
+
+
+ /**
+ * Get next ID that may be used for multiplexing on this connection.
+ *
+ * @return (hopefully) unique ID used for multiplexing.
+ **/
+ uint32_t GetNextID()
+ {
+ uint32_t ret = _currentID;
+ if (ret == FNET_NOID)
+ ret += 2;
+ _currentID = ret + 2;
+ return ret;
+ }
+
+ /**
+ * Wait until the given channel is no longer the target of a
+ * callback. The caller must have exclusive access to this object.
+ * Note that calling this method from the callback you want to wait
+ * for completion of will generate a deadlock (waiting for
+ * yourself).
+ *
+ * @param channel the channel you want to wait for callback
+ * completion on. Note that when broadcasting
+ * control packets all channels are callback
+ * targets at the same time.
+ **/
+ void WaitCallback(FNET_Channel *channel)
+ {
+ while (_flags._inCallback
+ && (_callbackTarget == channel || _callbackTarget == NULL)) {
+ _flags._callbackWait = true;
+ Wait();
+ }
+ }
+
+ /**
+ * This method is called before a packet is delivered through a
+ * callback. The object must be locked when this method is
+ * called. When this method returns the object will be in callback
+ * mode, but not locked.
+ **/
+ void BeforeCallback(FNET_Channel *channel)
+ {
+ _flags._inCallback = true;
+ _callbackTarget = channel;
+ Unlock();
+ }
+
+ /**
+ * This method is called after a packet has been delivered through a
+ * callback. The object must be in callback mode when this method is
+ * called, but not locked. When this method returns the object is no
+ * longer in callback mode, but locked.
+ **/
+ void AfterCallback()
+ {
+ Lock();
+ _flags._inCallback = false;
+ if (_flags._callbackWait) {
+ _flags._callbackWait = false;
+ Broadcast();
+ }
+ }
+
+ /**
+ * @return a string describing the given connection state.
+ **/
+ const char *GetStateString(State state);
+
+ /**
+ * Set connection state.
+ *
+ * @param state new state for this connection.
+ **/
+ void SetState(State state);
+
+ /**
+ * Handle incoming packet. The packet data is located in the input
+ * databuffer. This method tries to decode the packet data into a
+ * packet object and deliver it on the appropriate channel.
+ *
+ * @param plen packet length
+ * @param pcode packet code
+ * @param chid channel id
+ **/
+ void HandlePacket(uint32_t plen, uint32_t pcode, uint32_t chid);
+
+ /**
+ * Read incoming data from socket.
+ *
+ * @return false if socket is broken.
+ **/
+ bool Read();
+
+ /**
+ * Write outgoing data to socket.
+ *
+ * @return false if socket is broken.
+ * @param direct is this a direct write (called directly from
+ * postpacket, without waiting for a write event)
+ **/
+ bool Write(bool direct);
+
+public:
+
+ /**
+ * Construct a connection in server aspect.
+ *
+ * @param owner the TransportThread object serving this connection
+ * @param streamer custom packet streamer
+ * @param serverAdapter object for custom channel creation
+ * @param mySocket the underlying socket used for IO
+ * @param spec listen spec
+ **/
+ FNET_Connection(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ FastOS_Socket *mySocket,
+ const char *spec);
+
+ /**
+ * Construct a connection in client aspect.
+ *
+ * @param owner the TransportThread object serving this connection
+ * @param streamer custom packet streamer
+ * @param serverAdapter object for custom channel creation
+ * @param adminHandler packet handler for admin channel
+ * @param adminContext context for admin channel
+ * @param context initial context for this connection
+ * @param mySocket the underlying socket used for IO
+ * @param spec connect spec
+ **/
+ FNET_Connection(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ FNET_IPacketHandler *adminHandler,
+ FNET_Context adminContext,
+ FNET_Context context,
+ FastOS_Socket *mySocket,
+ const char *spec);
+
+ /**
+ * Destructor.
+ **/
+ ~FNET_Connection();
+
+
+ /**
+ * Is this the server endpoint of a network connection?
+ *
+ * @return true/false
+ **/
+ bool IsServer() const { return (_currentID & 0x1) == 1; }
+
+
+ /**
+ * Is this the client endpoint of a network connection?
+ *
+ * @return true/false
+ **/
+ bool IsClient() const { return (_currentID & 0x1) == 0; }
+
+
+ /**
+ * Is the given channel id generated by our peer?
+ *
+ * @return true/false
+ **/
+ bool IsFromPeer(uint32_t chid) const {
+ return ((_currentID & 0x01) != (chid & 0x01));
+ }
+
+
+ /**
+ * Does this connection have the ability to accept incoming channels ?
+ *
+ * @return true/false
+ **/
+ bool CanAcceptChannels() const { return _serverAdapter != NULL; }
+
+
+ /**
+ * Assign a context to this connection.
+ * @param context the context for this connection
+ **/
+ void SetContext(FNET_Context context) { _context = context; }
+
+
+ /**
+ * @return the context for this connection
+ **/
+ FNET_Context GetContext() { return _context; }
+
+
+ /**
+ * @return a pointer to the context for this connection
+ **/
+ FNET_Context *GetContextPT() { return &_context; }
+
+
+ /**
+ * @return current connection state.
+ **/
+ State GetState() { return _state; }
+
+
+ /**
+ * Initialize connection. This method should be called directly
+ * after creation, before the object is shared. If this method
+ * returns false, the object should be deleted on the spot.
+ *
+ * @return success(true)/failure(false)
+ **/
+ bool Init();
+
+ /**
+ * Performs sync socket connect, sync connect post-setup and adds
+ * this connection to the event loop. Calling this function
+ * implicitly gives away 1 reference to this object.
+ **/
+ void ext_connect() override;
+
+ /**
+ * Register a cleanup handler to be invoked when this connection is
+ * about to be destructed.
+ *
+ * @param handler the cleanup handler
+ **/
+ void SetCleanupHandler(FNET_IConnectionCleanupHandler *handler);
+
+
+ /**
+ * Open a new channel on this connection. This method will return
+ * NULL of this connection is broken. Note that this method may only
+ * be invoked on connections in the client aspect. Channels on
+ * server aspect connections are opened with help from a server
+ * adapter.
+ *
+ * @return an object representing the opened channel or NULL.
+ * @param handler the owner of the channel to be opened.
+ * @param context application specific context.
+ * @param chid store the channel id at this location if not NULL
+ **/
+ FNET_Channel *OpenChannel(FNET_IPacketHandler *handler,
+ FNET_Context context,
+ uint32_t *chid = NULL);
+
+
+ /**
+ * Open a new channel on this connection that may only be used to
+ * send packets. This method will never return NULL. Note that this
+ * method may only be invoked on connections in the client
+ * aspect. Channels on server aspect connections are opened with
+ * help from a server adapter.
+ *
+ * @return an object representing the opened channel.
+ **/
+ FNET_Channel *OpenChannel();
+
+
+ /**
+ * Close a channel. Note that this only closes the network to
+ * application path; the application may still send packets on a
+ * closed channel.
+ *
+ * @return false if the channel was not found.
+ * @param channel the channel to close.
+ **/
+ bool CloseChannel(FNET_Channel *channel);
+
+
+ /**
+ * Free a channel object. The channel may not be used after this
+ * method has been called.
+ *
+ * @param channel the channel to free.
+ **/
+ void FreeChannel(FNET_Channel *channel);
+
+
+ /**
+ * Close and Free a channel in a single operation. This has the same
+ * effect as invoking @ref CloseChannel then @ref FreeChannel, but
+ * is more efficient.
+ *
+ * @param channel the channel to close and free.
+ **/
+ void CloseAndFreeChannel(FNET_Channel *channel);
+
+
+ /**
+ * Close the admin channel on a client connection. Invoking this
+ * method on connections in the server aspect will have no effect.
+ *
+ * This method is needed because on the client side the application
+ * does not own the actual admin channel object. The admin channel
+ * on server-side connections are handled as normal channels.
+ *
+ * After this method returns, no more packets will be delivered to
+ * the admin packet handler. In other words: this method is used to
+ * make the connection loose the reference to the admin packet
+ * handler in a thread-safe way, enabling the admin packet handler
+ * to be deleted or the like.
+ **/
+ void CloseAdminChannel();
+
+
+ /**
+ * Post a packet on the output queue. Note that the packet will not
+ * be sent if the connection is in CLOSING or CLOSED state. NOTE:
+ * packet handover (caller TO invoked object).
+ *
+ * @return false if connection was down, true otherwise.
+ * @param packet the packet to queue for sending.
+ * @param chid the channel id for the packet
+ **/
+ bool PostPacket(FNET_Packet *packet, uint32_t chid);
+
+
+ /**
+ * Obtain the number of packets located in the output queue for this
+ * connection. Note that this number is volatile and should only be
+ * used as an estimate. Also note that since a queue latching
+ * strategy is used, this method requires a mutex lock/unlock and is
+ * therefore not as cheap as may be expected.
+ *
+ * @return number of packets currently located in the output queue
+ * for this connection.
+ **/
+ uint32_t GetQueueLen();
+
+
+ /**
+ * Sync with this connection. When this method is invoked it will
+ * block until all packets currently posted on this connection is
+ * encoded into the output buffer. Also, the amount of data in the
+ * connection output buffer will be less than the amount given by
+ * FNET_Connection::FNET_WRITE_SIZE.
+ **/
+ void Sync();
+
+
+ /**
+ * Invoked by the io component superclass before the object is
+ * destructed. Will invoke the Cleanup method on the cleanup handler
+ * for this connection, if present.
+ **/
+ void CleanupHook();
+
+
+ /**
+ * Close this connection immidiately. NOTE: this method should only
+ * be called by the transport thread.
+ **/
+ void Close();
+
+
+ /**
+ * Called by the transport layer when a read event has occurred.
+ *
+ * @return false if connection broken, true otherwise.
+ **/
+ bool HandleReadEvent();
+
+
+ /**
+ * Called by the transport layer when a write event has occurred.
+ *
+ * @return false is connection broken, true otherwise.
+ **/
+ bool HandleWriteEvent();
+
+ /**
+ * @return Returns the size of this connection's output buffer.
+ */
+ uint32_t getOutputBufferSize() const { return _output.GetBufSize(); }
+
+ /**
+ * @return Returns the size of this connection's input buffer.
+ */
+ uint32_t getInputBufferSize() const { return _input.GetBufSize(); }
+
+};
+
diff --git a/fnet/src/vespa/fnet/connector.cpp b/fnet/src/vespa/fnet/connector.cpp
new file mode 100644
index 00000000000..706fa73d447
--- /dev/null
+++ b/fnet/src/vespa/fnet/connector.cpp
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+
+
+FNET_Connector::FNET_Connector(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ const char *spec,
+ int port, int backlog,
+ FastOS_SocketFactory *factory,
+ const char *strictBindHostName)
+ : FNET_IOComponent(owner, NULL, spec, /* time-out = */ false),
+ _streamer(streamer),
+ _serverAdapter(serverAdapter),
+ _serverSocket(NULL),
+ _strict(strictBindHostName != NULL)
+{
+ _serverSocket = new FastOS_ServerSocket(port, backlog, factory,
+ strictBindHostName);
+ assert(_serverSocket != NULL);
+ _ioc_socket = _serverSocket; // set socket 'manually'
+}
+
+
+FNET_Connector::~FNET_Connector()
+{
+ assert(_serverSocket->GetSocketEvent() == NULL);
+ delete _serverSocket;
+}
+
+
+bool
+FNET_Connector::Init()
+{
+ bool rc = true;
+
+ // check if strict binding went OK.
+ if (_strict) {
+ rc = _serverSocket->GetValidAddressFlag();
+ }
+
+ // configure socket for non-blocked listening
+ rc = (rc && (_serverSocket->SetSoBlocking(false))
+ && (_serverSocket->Listen()));
+
+ // print some debug output [XXX: remove this later]
+ if(rc) {
+ LOG(debug, "Connector(%s): TCP listen OK",
+ GetSpec());
+ EnableReadEvent(true);
+ } else {
+ LOG(warning, "Connector(%s): TCP listen FAILED",
+ GetSpec());
+ }
+ return rc;
+}
+
+
+void
+FNET_Connector::Close()
+{
+ SetSocketEvent(NULL);
+ _serverSocket->Close();
+}
+
+
+bool
+FNET_Connector::HandleReadEvent()
+{
+ FastOS_Socket *newSocket = NULL;
+ FNET_Connection *conn = NULL;
+
+ newSocket = _serverSocket->AcceptPlain();
+ if (newSocket != NULL) {
+ FNET_Transport &transport = Owner()->owner();
+ FNET_TransportThread *thread = transport.select_thread(newSocket, sizeof(FastOS_Socket));
+ conn = new FNET_Connection(thread, _streamer, _serverAdapter, newSocket, GetSpec());
+ if (newSocket->SetSoBlocking(false) && conn->Init()) {
+ conn->Owner()->Add(conn, false);
+ } else {
+ LOG(debug, "Connector(%s): failed to init incoming connection",
+ GetSpec());
+ delete conn; // this is legal.
+ }
+ }
+ return true;
+}
+
+
+bool
+FNET_Connector::HandleWriteEvent()
+{
+ LOG(debug, "Connector(%s): got write event, ignoring",
+ GetSpec());
+ EnableWriteEvent(false);
+ return true;
+}
diff --git a/fnet/src/vespa/fnet/connector.h b/fnet/src/vespa/fnet/connector.h
new file mode 100644
index 00000000000..db535d1c8bf
--- /dev/null
+++ b/fnet/src/vespa/fnet/connector.h
@@ -0,0 +1,85 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Class used to listen for incoming connections on a single TCP/IP
+ * port.
+ **/
+class FNET_Connector : public FNET_IOComponent
+{
+private:
+ FNET_IPacketStreamer *_streamer;
+ FNET_IServerAdapter *_serverAdapter;
+ FastOS_ServerSocket *_serverSocket;
+ bool _strict;
+
+ FNET_Connector(const FNET_Connector &);
+ FNET_Connector &operator=(const FNET_Connector &);
+
+public:
+ /**
+ * Construct a connector.
+ *
+ * @param owner the TransportThread object serving this connector
+ * @param streamer custom packet streamer
+ * @param serverAdapter object for custom channel creation
+ * @param spec listen spec for this connector
+ * @param port the port to listen on
+ * @param backlog accept queue length
+ * @param factory custom socket factory
+ * @param strictBindHostName bind strict to given hostname
+ **/
+ FNET_Connector(FNET_TransportThread *owner,
+ FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter,
+ const char *spec,
+ int port, int backlog = 500,
+ FastOS_SocketFactory *factory = NULL,
+ const char *strictBindHostName = NULL);
+ ~FNET_Connector();
+
+
+ /**
+ * Obtain the port number of the underlying server socket.
+ *
+ * @return port number
+ **/
+ uint32_t GetPortNumber() const { return _serverSocket->GetLocalPort(); }
+
+ /**
+ * Try to create a listening server socket at the port number
+ * specified in the constructor. The socket is set to
+ * non-blocking.
+ *
+ * @return true on success, false on fail
+ **/
+ bool Init();
+
+ /**
+ * Close this connector. This method must be called in the transport
+ * thread in order to avoid race conditions related to socket event
+ * registration, deregistration and triggering.
+ **/
+ void Close();
+
+ /**
+ * Called by the transport layer when a read event has occurred. If
+ * an incoming connection could be accepted, an event is posted on
+ * the transport thread event queue.
+ *
+ * @return false if connector is broken, true otherwise.
+ **/
+ bool HandleReadEvent();
+
+ /**
+ * Called by the transport layer when a write event has
+ * occurred. Note that this should never happen during normal
+ * operation. This method simply ignores the event, disables further
+ * write events and returns true.
+ *
+ * @return true.
+ **/
+ bool HandleWriteEvent();
+};
+
diff --git a/fnet/src/vespa/fnet/context.h b/fnet/src/vespa/fnet/context.h
new file mode 100644
index 00000000000..27e9533436f
--- /dev/null
+++ b/fnet/src/vespa/fnet/context.h
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class indicates the context of a packet. It is external to the
+ * packet class because a single packet may occur in many contexts at
+ * the same time (broadcast/multicast).
+ **/
+class FNET_Context
+{
+public:
+ FNET_Context() : _value() { _value.VOIDP = NULL; }
+ FNET_Context(uint32_t value) : _value() { _value.INT = value; }
+ FNET_Context(void *value) : _value() { _value.VOIDP = value; }
+ FNET_Context(FNET_Channel *value) : _value() { _value.CHANNEL = value; }
+
+ FNET_Context(FNET_IOComponent *value) : _value()
+ { _value.IOC = value; }
+ FNET_Context(FNET_Connector *value) : _value()
+ { _value.CONNECTOR = value; }
+ FNET_Context(FNET_Connection *value) : _value()
+ { _value.CONNECTION = value; }
+ FNET_Context(FNET_IExecutable *value) : _value()
+ { _value.EXECUTABLE = value; }
+
+ union {
+ uint32_t INT;
+ void *VOIDP;
+ FNET_Channel *CHANNEL;
+ FNET_IOComponent *IOC;
+ FNET_Connector *CONNECTOR;
+ FNET_Connection *CONNECTION;
+ FNET_IExecutable *EXECUTABLE;
+ } _value;
+
+ void Print(uint32_t indent = 0)
+ {
+ printf("%*sFNET_Context {\n", indent, "");
+ printf("%*s Value[INT] : %d\n", indent, "", _value.INT);
+ printf("%*s Value[VOIDP]: %p\n", indent, "", _value.VOIDP);
+ printf("%*s}\n", indent, "");
+ }
+};
+
diff --git a/fnet/src/vespa/fnet/controlpacket.cpp b/fnet/src/vespa/fnet/controlpacket.cpp
new file mode 100644
index 00000000000..5c17b4c2441
--- /dev/null
+++ b/fnet/src/vespa/fnet/controlpacket.cpp
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+void
+FNET_ControlPacket::Free()
+{
+}
+
+bool
+FNET_ControlPacket::IsRegularPacket()
+{
+ return false;
+}
+
+bool
+FNET_ControlPacket::IsControlPacket()
+{
+ return true;
+}
+
+uint32_t
+FNET_ControlPacket::GetCommand()
+{
+ return _command;
+}
+
+bool
+FNET_ControlPacket::IsChannelLostCMD()
+{
+ return _command == FNET_CMD_CHANNEL_LOST;
+}
+
+bool
+FNET_ControlPacket::IsTimeoutCMD()
+{
+ return _command == FNET_CMD_TIMEOUT;
+}
+
+bool
+FNET_ControlPacket::IsBadPacketCMD()
+{
+ return _command == FNET_CMD_BAD_PACKET;
+}
+
+uint32_t
+FNET_ControlPacket::GetPCODE()
+{
+ return FNET_NOID;
+}
+
+uint32_t
+FNET_ControlPacket::GetLength()
+{
+ return 0;
+}
+
+void
+FNET_ControlPacket::Encode(FNET_DataBuffer *)
+{
+ abort();
+}
+
+bool
+FNET_ControlPacket::Decode(FNET_DataBuffer *, uint32_t)
+{
+ abort(); return false;
+}
+
+vespalib::string
+FNET_ControlPacket::Print(uint32_t indent)
+{
+ return vespalib::make_string("%*sFNET_ControlPacket { command = %d }\n",
+ indent, "", _command);
+}
+
+FNET_ControlPacket
+FNET_ControlPacket::NoCommand(FNET_CMD_NOCOMMAND);
+
+FNET_ControlPacket
+FNET_ControlPacket::ChannelLost(FNET_CMD_CHANNEL_LOST);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCAdd(FNET_CMD_IOC_ADD);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCEnableRead(FNET_CMD_IOC_ENABLE_READ);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCDisableRead(FNET_CMD_IOC_DISABLE_READ);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCEnableWrite(FNET_CMD_IOC_ENABLE_WRITE);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCDisableWrite(FNET_CMD_IOC_DISABLE_WRITE);
+
+FNET_ControlPacket
+FNET_ControlPacket::IOCClose(FNET_CMD_IOC_CLOSE);
+
+FNET_ControlPacket
+FNET_ControlPacket::Execute(FNET_CMD_EXECUTE);
+
+FNET_ControlPacket
+FNET_ControlPacket::Timeout(FNET_CMD_TIMEOUT);
+
+FNET_ControlPacket
+FNET_ControlPacket::BadPacket(FNET_CMD_BAD_PACKET);
diff --git a/fnet/src/vespa/fnet/controlpacket.h b/fnet/src/vespa/fnet/controlpacket.h
new file mode 100644
index 00000000000..35b6b8470c6
--- /dev/null
+++ b/fnet/src/vespa/fnet/controlpacket.h
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Packets of this type may be used to send simple control signals
+ * between components in the application. Control packets only contain
+ * a single unsigned integer. This integer denotes the type of control
+ * packet. The effect of a control packet depends on the packet
+ * context it is delivered in. Control packets are also special in
+ * that the Free method is overridden to do nothing. This means that
+ * control packets must be deleted explicitly, which correlates nicely
+ * with their static nature (you only need one instance of each type
+ * of control packet). Control packets are mainly used for FNET
+ * related signals and events, but the application is free to use the
+ * control packets defined by FNET for it's own purposes. It may also
+ * define it's own control packets as long as the command values are
+ * greater than or equal to 5000. Note that control packets may NOT be
+ * sent across the network.
+ **/
+class FNET_ControlPacket : public FNET_Packet
+{
+private:
+ uint32_t _command;
+
+ FNET_ControlPacket(const FNET_ControlPacket &);
+ FNET_ControlPacket &operator=(const FNET_ControlPacket &);
+
+public:
+ FNET_ControlPacket(uint32_t command) : _command(command) {}
+
+ enum {
+ FNET_CMD_NOCOMMAND = 0,
+ FNET_CMD_CHANNEL_LOST,
+ FNET_CMD_IOC_ADD,
+ FNET_CMD_IOC_ENABLE_READ,
+ FNET_CMD_IOC_DISABLE_READ,
+ FNET_CMD_IOC_ENABLE_WRITE,
+ FNET_CMD_IOC_DISABLE_WRITE,
+ FNET_CMD_IOC_CLOSE,
+ FNET_CMD_EXECUTE,
+ FNET_CMD_TIMEOUT,
+ FNET_CMD_BAD_PACKET,
+
+ FNET_CMD_LASTVALUE = FNET_CMD_BAD_PACKET
+ };
+
+ static FNET_ControlPacket NoCommand;
+ static FNET_ControlPacket ChannelLost;
+ static FNET_ControlPacket IOCAdd;
+ static FNET_ControlPacket IOCEnableRead;
+ static FNET_ControlPacket IOCDisableRead;
+ static FNET_ControlPacket IOCEnableWrite;
+ static FNET_ControlPacket IOCDisableWrite;
+ static FNET_ControlPacket IOCClose;
+ static FNET_ControlPacket Execute;
+ static FNET_ControlPacket Timeout;
+ static FNET_ControlPacket BadPacket;
+
+ /**
+ * This method is empty.
+ **/
+ virtual void Free();
+
+ /**
+ * @return false
+ **/
+ virtual bool IsRegularPacket();
+
+ /**
+ * @return true
+ **/
+ virtual bool IsControlPacket();
+
+ virtual uint32_t GetCommand();
+ virtual bool IsChannelLostCMD();
+ virtual bool IsTimeoutCMD();
+ virtual bool IsBadPacketCMD();
+
+ /**
+ * @return FNET_NOID
+ **/
+ virtual uint32_t GetPCODE();
+
+ /**
+ * @return 0
+ **/
+ virtual uint32_t GetLength();
+
+ /**
+ * This method should never be called and will abort the program.
+ **/
+ virtual void Encode(FNET_DataBuffer *);
+
+ /**
+ * This method should never be called and will abort the program.
+ **/
+ virtual bool Decode(FNET_DataBuffer *, uint32_t);
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
+
diff --git a/fnet/src/vespa/fnet/databuffer.cpp b/fnet/src/vespa/fnet/databuffer.cpp
new file mode 100644
index 00000000000..3239dbf5087
--- /dev/null
+++ b/fnet/src/vespa/fnet/databuffer.cpp
@@ -0,0 +1,139 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+using vespalib::DefaultAlloc;
+
+FNET_DataBuffer::FNET_DataBuffer(uint32_t len)
+ : _bufstart(NULL),
+ _bufend(NULL),
+ _datapt(NULL),
+ _freept(NULL)
+{
+ if (len > 0 && len < 256)
+ len = 256;
+
+ if (len > 0) {
+ DefaultAlloc(len).swap(_ownedBuf);
+ memset(_ownedBuf.get(), 0x55, len);
+ _bufstart = static_cast<char *>(_ownedBuf.get());
+ assert(_bufstart != NULL);
+ } else { // len == 0
+ _bufstart = NULL;
+ }
+ _bufend = _bufstart + len;
+ _datapt = _freept = _bufstart;
+}
+
+
+FNET_DataBuffer::FNET_DataBuffer(char *buf, uint32_t len)
+ : _bufstart(buf),
+ _bufend(buf + len),
+ _datapt(_bufstart),
+ _freept(_bufstart)
+{
+}
+
+
+FNET_DataBuffer::~FNET_DataBuffer()
+{
+}
+
+
+void
+FNET_DataBuffer::FreeToData(uint32_t len)
+{
+ assert(GetFreeLen() >= len);
+ _freept += len;
+}
+
+
+void
+FNET_DataBuffer::DeadToData(uint32_t len)
+{
+ assert(GetDeadLen() >= len);
+ _datapt -= len;
+}
+
+
+void
+FNET_DataBuffer::DataToFree(uint32_t len)
+{
+ assert(GetDataLen() >= len);
+ _freept -= len;
+}
+
+
+bool
+FNET_DataBuffer::Shrink(uint32_t newsize)
+{
+ if (GetBufSize() <= newsize || GetDataLen() > newsize) {
+ return false;
+ }
+
+ DefaultAlloc newBuf(newsize);
+ memset(newBuf.get(), 0x55, newsize);
+ memcpy(newBuf.get(), _datapt, GetDataLen());
+ _ownedBuf.swap(newBuf);
+ _bufstart = static_cast<char *>(_ownedBuf.get());
+ _freept = _bufstart + GetDataLen();
+ _datapt = _bufstart;
+ _bufend = _bufstart + newsize;
+ return true;
+}
+
+
+void
+FNET_DataBuffer::Pack(uint32_t needbytes)
+{
+ if ((GetDeadLen() + GetFreeLen()) < needbytes ||
+ (GetDeadLen() + GetFreeLen()) * 4 < GetDataLen())
+ {
+ uint32_t bufsize = GetBufSize() * 2;
+ if (bufsize < 256) {
+ bufsize = 256;
+ }
+ while (bufsize - GetDataLen() < needbytes)
+ bufsize *= 2;
+
+ DefaultAlloc newBuf(bufsize);
+ memset(newBuf.get(), 0x55, bufsize);
+ memcpy(newBuf.get(), _datapt, GetDataLen());
+ _ownedBuf.swap(newBuf);
+ _bufstart = static_cast<char *>(_ownedBuf.get());
+ _freept = _bufstart + GetDataLen();
+ _datapt = _bufstart;
+ _bufend = _bufstart + bufsize;
+ } else {
+ memmove(_bufstart, _datapt, GetDataLen());
+ _freept = _bufstart + GetDataLen();
+ _datapt = _bufstart;
+ }
+}
+
+
+bool
+FNET_DataBuffer::Equals(FNET_DataBuffer *other)
+{
+ if (GetDataLen() != other->GetDataLen())
+ return false;
+ return memcmp(GetData(), other->GetData(), GetDataLen()) == 0;
+}
+
+
+void
+FNET_DataBuffer::HexDump()
+{
+ char *pt = _datapt;
+ printf("*** FNET_DataBuffer HexDump BEGIN ***\n");
+ uint32_t i = 0;
+ while (pt < _freept) {
+ printf("%x ", (unsigned char) *pt++);
+ if ((++i % 16) == 0)
+ printf("\n");
+ }
+ if ((i % 16) != 0)
+ printf("\n");
+ printf("*** FNET_DataBuffer HexDump END ***\n");
+}
diff --git a/fnet/src/vespa/fnet/databuffer.h b/fnet/src/vespa/fnet/databuffer.h
new file mode 100644
index 00000000000..23802d2ea90
--- /dev/null
+++ b/fnet/src/vespa/fnet/databuffer.h
@@ -0,0 +1,663 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/compress.h>
+#include <vespa/vespalib/util/alloc.h>
+
+/**
+ * This is a buffer that may hold the stream representation of
+ * packets. It has helper methods in order to simplify and standardize
+ * packet encoding and decoding. The default byte order for
+ * reading/writing integers is internet order (big endian). The
+ * methods with a 'Reverse' suffix assume that the data in the buffer
+ * is stored in reverse internet order (little endian).
+ *
+ * A databuffer covers a continuous chunk of memory that is split into
+ * 3 parts; 'dead', 'data' and 'free'. 'dead' denotes the space at the
+ * beginning of the buffer that may not currently be utilized, 'data'
+ * denotes the part that contains the actual data and 'free' denotes
+ * the free space at the end of the buffer. Initially, the 'dead' and
+ * 'data' parts are empty, and the 'free' part spans the entire
+ * buffer. When writing to the buffer, bytes are transfered from the
+ * 'free' part to the 'data' part of the buffer. When reading from the
+ * buffer, bytes are transferred from the 'data' part to the 'dead'
+ * part of the buffer. If the 'free' part of the buffer becomes empty,
+ * the data will be relocated within the buffer and/or a bigger buffer
+ * will be allocated.
+ **/
+class FNET_DataBuffer
+{
+private:
+ char *_bufstart;
+ char *_bufend;
+ char *_datapt;
+ char *_freept;
+ vespalib::DefaultAlloc _ownedBuf;
+
+ FNET_DataBuffer(const FNET_DataBuffer &);
+ FNET_DataBuffer &operator=(const FNET_DataBuffer &);
+
+public:
+
+ /**
+ * Construct a databuffer.
+ *
+ * @param len the initial size of the buffer.
+ **/
+ FNET_DataBuffer(uint32_t len = 1024);
+
+ /**
+ * Construct a databuffer using externally allocated memory. Note
+ * that the externally allocated memory will not be freed by the
+ * databuffer.
+ *
+ * @param buf pointer to preallocated memory
+ * @param len length of preallocated memory
+ **/
+ FNET_DataBuffer(char *buf, uint32_t len);
+ ~FNET_DataBuffer();
+
+ /**
+ * @return a pointer to the dead part of this buffer.
+ **/
+ char *GetDead() { return _bufstart; }
+
+ /**
+ * @return a pointer to the data part of this buffer.
+ **/
+ char *GetData() { return _datapt; }
+
+ /**
+ * @return a pointer to the free part of this buffer.
+ **/
+ char *GetFree() { return _freept; }
+
+ /**
+ * @return the length of the dead part of this buffer.
+ **/
+ uint32_t GetDeadLen() const { return _datapt - _bufstart; }
+
+ /**
+ * @return the length of the data part of this buffer.
+ **/
+ uint32_t GetDataLen() const { return _freept - _datapt; }
+
+ /**
+ * @return the length of the free part of this buffer.
+ **/
+ uint32_t GetFreeLen() const { return _bufend - _freept; }
+
+ /**
+ * @return the length of the entire buffer.
+ **/
+ uint32_t GetBufSize() const { return _bufend - _bufstart; }
+
+
+ /**
+ * 'Move' bytes from the free part to the data part of this buffer.
+ * This will have the same effect as if the data already located in
+ * the free part of this buffer was written to the buffer.
+ *
+ * @param len number of bytes to 'move'.
+ **/
+ void FreeToData(uint32_t len);
+
+ /**
+ * 'Move' bytes from the data part to the dead part of this buffer.
+ * This will have the effect of discarding data without having to
+ * read it.
+ *
+ * @param len number of bytes to 'move'.
+ **/
+ void DataToDead(uint32_t len) { _datapt += len; }
+
+
+ /**
+ * 'Move' bytes from the dead part to the data part of this buffer.
+ * This may be used to undo a read operation (un-discarding
+ * data). Note that writing to the buffer may result in
+ * reorganization making the data part of the buffer disappear.
+ *
+ * @param len number of bytes to 'move'.
+ **/
+ void DeadToData(uint32_t len);
+
+ /**
+ * 'Move' bytes from the data part to the free part of this buffer.
+ * This may be used to undo a write operation; discarding the data
+ * most recently written.
+ *
+ * @param len number of bytes to 'move'.
+ **/
+ void DataToFree(uint32_t len);
+
+
+ /**
+ * Clear this buffer.
+ **/
+ void Clear() { _datapt = _freept = _bufstart; }
+
+
+ /**
+ * Shrink this buffer. The given value is the new wanted size of
+ * this buffer. If the buffer is already smaller or equal in size
+ * compared to the given value, no resizing is performed and false
+ * is returned (Use the @ref EnsureFree method to ensure free
+ * space). If the buffer currently contains more data than can be
+ * held in a buffer of the wanted size, no resizing is performed and
+ * false is returned.
+ *
+ * @param newsize the wanted new size of this buffer (in bytes).
+ * @return true if the buffer was shrunk, false otherwise.
+ **/
+ bool Shrink(uint32_t newsize);
+
+ /**
+ * Reorganize this buffer such that the dead part becomes empty and
+ * the free part contains at least the given number of
+ * bytes. Allocate a bigger buffer if needed.
+ *
+ * @param needbytes required size of free part.
+ **/
+ void Pack(uint32_t needbytes);
+
+ /**
+ * Ensure that the free part contains at least the given number of
+ * bytes. This method invokes the @ref Pack method if the free part
+ * of the buffer is too small.
+ *
+ * @param needbytes required size of free part.
+ **/
+ void EnsureFree(uint32_t needbytes)
+ {
+ if (needbytes > GetFreeLen())
+ Pack(needbytes);
+ }
+
+
+ /**
+ * Write an 8-bit unsigned integer to this buffer.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt8(uint8_t n)
+ {
+ EnsureFree(1);
+ *_freept++ = (char)n;
+ }
+
+ /**
+ * Write a 16-bit unsigned integer to this buffer.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt16(uint16_t n)
+ {
+ EnsureFree(2);
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 2;
+ }
+
+ /**
+ * Write a 32-bit unsigned integer to this buffer.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt32(uint32_t n)
+ {
+ EnsureFree(4);
+ _freept[3] = (char)n;
+ n >>= 8;
+ _freept[2] = (char)n;
+ n >>= 8;
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 4;
+ }
+
+ /**
+ * Write a 64-bit unsigned integer to this buffer.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt64(uint64_t n)
+ {
+ EnsureFree(8);
+ _freept[7] = (char)n;
+ n >>= 8;
+ _freept[6] = (char)n;
+ n >>= 8;
+ _freept[5] = (char)n;
+ n >>= 8;
+ _freept[4] = (char)n;
+ n >>= 8;
+ _freept[3] = (char)n;
+ n >>= 8;
+ _freept[2] = (char)n;
+ n >>= 8;
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 8;
+ }
+
+
+ /**
+ * Write an 8-bit unsigned integer to this buffer. Skip checking for
+ * free space.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt8Fast(uint8_t n)
+ {
+ *_freept++ = (char)n;
+ }
+
+ /**
+ * Write a 16-bit unsigned integer to this buffer. Skip checking for
+ * free space.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt16Fast(uint16_t n)
+ {
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 2;
+ }
+
+ /**
+ * Write a 32-bit unsigned integer to this buffer. Skip checking for
+ * free space.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt32Fast(uint32_t n)
+ {
+ _freept[3] = (char)n;
+ n >>= 8;
+ _freept[2] = (char)n;
+ n >>= 8;
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 4;
+ }
+
+ /**
+ * Write a 64-bit unsigned integer to this buffer. Skip checking for
+ * free space.
+ *
+ * @param n the integer to write.
+ **/
+ void WriteInt64Fast(uint64_t n)
+ {
+ _freept[7] = (char)n;
+ n >>= 8;
+ _freept[6] = (char)n;
+ n >>= 8;
+ _freept[5] = (char)n;
+ n >>= 8;
+ _freept[4] = (char)n;
+ n >>= 8;
+ _freept[3] = (char)n;
+ n >>= 8;
+ _freept[2] = (char)n;
+ n >>= 8;
+ _freept[1] = (char)n;
+ n >>= 8;
+ _freept[0] = (char)n;
+ _freept += 8;
+ }
+
+ /**
+ * Will write a positive number in compressed form.
+ * @param number to write
+ **/
+ void writeCompressedPositive(uint64_t n) {
+ _freept += vespalib::compress::Integer::compressPositive(n, _freept);
+ }
+
+ /**
+ * Will write a number in compressed form.
+ * @param number to write
+ **/
+ void writeCompressed(int64_t n) {
+ _freept += vespalib::compress::Integer::compress(n, _freept);
+ }
+
+ /**
+ * Will read a compressed positive integer.
+ * @return uncompressed number
+ **/
+ uint64_t readCompressedPositiveInteger() {
+ uint64_t n;
+ _datapt += vespalib::compress::Integer::decompressPositive(n, _datapt);
+ return n;
+ }
+
+ /**
+ * Will read a compressed integer.
+ * @return uncompressed number
+ **/
+ int64_t readCompressedInteger() {
+ int64_t n;
+ _datapt += vespalib::compress::Integer::decompress(n, _datapt);
+ return n;
+ }
+
+ /**
+ * @return Number of bytes the compressed positiv number will occupy.
+ **/
+ static size_t getCompressedPositiveLength(uint64_t n) {
+ return vespalib::compress::Integer::compressedPositiveLength(n);
+ }
+
+ /**
+ * @return Number of bytes the compressed positiv number will occupy.
+ **/
+ static size_t getCompressedLength(int64_t n) {
+ return vespalib::compress::Integer::compressedLength(n);
+ }
+
+ /**
+ * Read an 8-bit unsigned integer from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint8_t ReadInt8()
+ {
+ return (unsigned char)(*_datapt++);
+ }
+
+ /**
+ * Read a 16-bit unsigned integer from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint16_t ReadInt16()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 2;
+ return ((*tmp << 8) + *(tmp + 1));
+ }
+
+ /**
+ * Read a 16-bit unsigned integer stored in reverse internet order
+ * from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint16_t ReadInt16Reverse()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 2;
+ return ((*(tmp + 1) << 8) + *tmp);
+ }
+
+ /**
+ * Read a 32-bit unsigned integer from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint32_t ReadInt32()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 4;
+ return
+ ((((((uint32_t)(*tmp << 8) + *(tmp + 1)) << 8)
+ + *(tmp + 2)) << 8) + *(tmp + 3));
+ }
+
+ /**
+ * Read a 32-bit unsigned integer stored in reverse internet order
+ * from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint32_t ReadInt32Reverse()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 4;
+ return
+ ((((((uint32_t)(*(tmp + 3) << 8) + *(tmp + 2)) << 8)
+ + *(tmp + 1)) << 8) + *tmp);
+ }
+
+ /**
+ * Read a 64-bit unsigned integer from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint64_t ReadInt64()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 8;
+ return
+ ((((((((((((((uint64_t)(*tmp << 8) + *(tmp + 1)) << 8)
+ + *(tmp + 2)) << 8) + *(tmp + 3)) << 8)
+ + *(tmp + 4)) << 8) + *(tmp + 5)) << 8)
+ + *(tmp + 6)) << 8) + *(tmp + 7));
+ }
+
+ /**
+ * Read a 64-bit unsigned integer stored in reverse internet order
+ * from this buffer.
+ *
+ * @return the integer that has been read.
+ **/
+ uint64_t ReadInt64Reverse()
+ {
+ unsigned char *tmp = (unsigned char *)(_datapt);
+ _datapt += 8;
+ return
+ ((((((((((((((uint64_t)(*(tmp + 7) << 8) + *(tmp + 6)) << 8)
+ + *(tmp + 5)) << 8) + *(tmp + 4)) << 8)
+ + *(tmp + 3)) << 8) + *(tmp + 2)) << 8)
+ + *(tmp + 1)) << 8) + *tmp);
+ }
+
+
+ /**
+ * Peek at an 8-bit unsigned integer in this buffer. Unlike a read
+ * operation, this will not modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint8_t PeekInt8(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 1);
+ return (uint8_t) *(_datapt + offset);
+ }
+
+ /**
+ * Peek at a 16-bit unsigned integer in this buffer. Unlike a read
+ * operation, this will not modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint16_t PeekInt16(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 2);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return (uint16_t) ((*tmp << 8) + *(tmp + 1));
+ }
+
+ /**
+ * Peek at a 16-bit unsigned integer stored in reverse internet
+ * order in this buffer. Unlike a read operation, this will not
+ * modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint16_t PeekInt16Reverse(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 2);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return (uint16_t) ((*(tmp + 1) << 8) + *tmp);
+ }
+
+ /**
+ * Peek at a 32-bit unsigned integer in this buffer. Unlike a read
+ * operation, this will not modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint32_t PeekInt32(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 4);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return
+ ((((((uint32_t)(*tmp << 8) + *(tmp + 1)) << 8)
+ + *(tmp + 2)) << 8) + *(tmp + 3));
+ }
+
+ /**
+ * Peek at a 32-bit unsigned integer stored in reverse internet
+ * order in this buffer. Unlike a read operation, this will not
+ * modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint32_t PeekInt32Reverse(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 4);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return
+ ((((((uint32_t)(*(tmp + 3) << 8) + *(tmp + 2)) << 8)
+ + *(tmp + 1)) << 8) + *tmp);
+ }
+
+ /**
+ * Peek at a 64-bit unsigned integer in this buffer. Unlike a read
+ * operation, this will not modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint64_t PeekInt64(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 8);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return
+ ((((((((((((((uint64_t)(*tmp << 8) + *(tmp + 1)) << 8)
+ + *(tmp + 2)) << 8) + *(tmp + 3)) << 8)
+ + *(tmp + 4)) << 8) + *(tmp + 5)) << 8)
+ + *(tmp + 6)) << 8) + *(tmp + 7));
+ }
+
+ /**
+ * Peek at a 64-bit unsigned integer stored in reverse internet
+ * order in this buffer. Unlike a read operation, this will not
+ * modify the buffer.
+ *
+ * @param offset offset of the integer to access.
+ * @return value of the accessed integer.
+ **/
+ uint64_t PeekInt64Reverse(uint32_t offset)
+ {
+ assert(GetDataLen() >= offset + 8);
+ unsigned char *tmp = (unsigned char *)(_datapt + offset);
+ return
+ ((((((((((((((uint64_t)(*(tmp + 7) << 8) + *(tmp + 6)) << 8)
+ + *(tmp + 5)) << 8) + *(tmp + 4)) << 8)
+ + *(tmp + 3)) << 8) + *(tmp + 2)) << 8)
+ + *(tmp + 1)) << 8) + *tmp);
+ }
+
+
+ /**
+ * Write bytes to this buffer.
+ *
+ * @param src source byte buffer.
+ * @param len number of bytes to write.
+ **/
+ void WriteBytes(const void *src, uint32_t len)
+ {
+ EnsureFree(len);
+ memcpy(_freept, src, len);
+ _freept += len;
+ }
+
+ /**
+ * Write bytes to this buffer. Skip checking for free space.
+ *
+ * @param src source byte buffer.
+ * @param len number of bytes to write.
+ **/
+ void WriteBytesFast(const void *src, uint32_t len)
+ {
+ memcpy(_freept, src, len);
+ _freept += len;
+ }
+
+ /**
+ * Read bytes from this buffer.
+ *
+ * @param dst destination byte buffer.
+ * @param len number of bytes to read.
+ **/
+ void ReadBytes(void *dst, uint32_t len)
+ {
+ memcpy(dst, _datapt, len);
+ _datapt += len;
+ }
+
+ /**
+ * Peek at bytes in this buffer. Unlike a read operation, this will
+ * not modify the buffer.
+ *
+ * @param dst destination byte buffer.
+ * @param len number of bytes to extract.
+ * @param offset byte offset into the buffer.
+ **/
+ void PeekBytes(void *dst, uint32_t len, uint32_t offset)
+ {
+ assert(_freept >= _datapt + offset + len);
+ memcpy(dst, _datapt + offset, len);
+ }
+
+ /**
+ * Check if the data stored in this buffer equals the data stored in
+ * another buffer.
+ *
+ * @return true(equal)/false(not equal)
+ * @param other the other buffer.
+ **/
+ bool Equals(FNET_DataBuffer *other);
+
+ /**
+ * Print a human-readable representation of this buffer to
+ * stdout. This method may be used for debugging purposes.
+ **/
+ void HexDump();
+
+ /**
+ * Run some asserts to verify that this databuffer is in a legal
+ * state.
+ **/
+ void AssertValid()
+ {
+ assert(_bufstart <= _datapt);
+ assert(_datapt <= _freept);
+ assert(_freept <= _bufend);
+ }
+
+ void resetIfEmpty() {
+ if (GetDataLen() == 0) {
+ Clear();
+ }
+ }
+
+};
+
diff --git a/fnet/src/vespa/fnet/dummypacket.cpp b/fnet/src/vespa/fnet/dummypacket.cpp
new file mode 100644
index 00000000000..27ee4741d06
--- /dev/null
+++ b/fnet/src/vespa/fnet/dummypacket.cpp
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+
+FNET_DummyPacket::FNET_DummyPacket()
+{
+}
+
+bool
+FNET_DummyPacket::IsRegularPacket()
+{
+ return false;
+}
+
+bool
+FNET_DummyPacket::IsControlPacket()
+{
+ return false;
+}
+
+uint32_t
+FNET_DummyPacket::GetPCODE()
+{
+ return FNET_NOID;
+}
+
+uint32_t
+FNET_DummyPacket::GetLength()
+{
+ return 0;
+}
+
+void
+FNET_DummyPacket::Encode(FNET_DataBuffer *)
+{
+ abort();
+}
+
+bool
+FNET_DummyPacket::Decode(FNET_DataBuffer *, uint32_t)
+{
+ abort(); return false;
+}
+
+vespalib::string
+FNET_DummyPacket::Print(uint32_t indent)
+{
+ return vespalib::make_string("%*sFNET_DummyPacket {}\n", indent, "");
+}
diff --git a/fnet/src/vespa/fnet/dummypacket.h b/fnet/src/vespa/fnet/dummypacket.h
new file mode 100644
index 00000000000..e7db26277bf
--- /dev/null
+++ b/fnet/src/vespa/fnet/dummypacket.h
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * A dummy packet is neither a regular packet nor a control
+ * packet. The idea with this class is that you subclass it to make
+ * packets that perform certain tasks when they are freed. The default
+ * Free method simply deletes the packet object, so you may chose to
+ * override either the Free method or the destructor, depending on the
+ * intended lifetime of the packet.
+ **/
+class FNET_DummyPacket : public FNET_Packet
+{
+public:
+ /**
+ * Empty constructor.
+ **/
+ FNET_DummyPacket();
+
+ /**
+ * @return false
+ **/
+ virtual bool IsRegularPacket();
+
+ /**
+ * @return false
+ **/
+ virtual bool IsControlPacket();
+
+ /**
+ * @return FNET_NOID
+ **/
+ virtual uint32_t GetPCODE();
+
+ /**
+ * @return 0
+ **/
+ virtual uint32_t GetLength();
+
+ /**
+ * This method should never be called and will abort the program.
+ **/
+ virtual void Encode(FNET_DataBuffer *);
+
+ /**
+ * This method should never be called and will abort the program.
+ **/
+ virtual bool Decode(FNET_DataBuffer *, uint32_t);
+
+ /**
+ * Identify as dummy packet.
+ **/
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
+
diff --git a/fnet/src/vespa/fnet/fdselector.cpp b/fnet/src/vespa/fnet/fdselector.cpp
new file mode 100644
index 00000000000..23ca8e4dc79
--- /dev/null
+++ b/fnet/src/vespa/fnet/fdselector.cpp
@@ -0,0 +1,104 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+
+
+FNET_FDSelector::FNET_FDSelector(FNET_Transport *transport, int fd,
+ FNET_IFDSelectorHandler *handler,
+ FNET_Context context)
+ : FNET_IOComponent(transport->select_thread(&fd, sizeof(fd)), &_fdSocket, FDSpec(fd).spec(), false),
+ _fd(fd),
+ _fdSocket(fd),
+ _handler(handler),
+ _context(context),
+ _eventBusy(false),
+ _eventWait(false)
+{
+ AddRef_NoLock();
+ Owner()->Add(this, false);
+}
+
+
+void
+FNET_FDSelector::updateReadSelection(bool wantRead)
+{
+ if (wantRead) {
+ Owner()->EnableRead(this);
+ } else {
+ Owner()->DisableRead(this);
+ }
+}
+
+
+void
+FNET_FDSelector::updateWriteSelection(bool wantWrite)
+{
+ if (wantWrite) {
+ Owner()->EnableWrite(this);
+ } else {
+ Owner()->DisableWrite(this);
+ }
+}
+
+
+void
+FNET_FDSelector::dispose()
+{
+ Lock();
+ waitEvent();
+ _handler = NULL;
+ Unlock();
+ Owner()->Close(this, false);
+}
+
+
+FNET_FDSelector::~FNET_FDSelector()
+{
+ assert(_fdSocket.GetSocketEvent() == NULL);
+}
+
+
+void
+FNET_FDSelector::Close()
+{
+ SetSocketEvent(NULL);
+}
+
+
+bool
+FNET_FDSelector::HandleReadEvent()
+{
+ if (!_flags._ioc_readEnabled) {
+ return true;
+ }
+ Lock();
+ FNET_IFDSelectorHandler *handler = _handler;
+ beforeEvent();
+ if (handler != NULL) {
+ handler->readEvent(this);
+ }
+ afterEvent();
+ Unlock();
+ return true;
+}
+
+
+bool
+FNET_FDSelector::HandleWriteEvent()
+{
+ if (!_flags._ioc_writeEnabled) {
+ return true;
+ }
+ Lock();
+ FNET_IFDSelectorHandler *handler = _handler;
+ beforeEvent();
+ if (handler != NULL) {
+ handler->writeEvent(this);
+ }
+ afterEvent();
+ Unlock();
+ return true;
+}
diff --git a/fnet/src/vespa/fnet/fdselector.h b/fnet/src/vespa/fnet/fdselector.h
new file mode 100644
index 00000000000..4d07baecdd0
--- /dev/null
+++ b/fnet/src/vespa/fnet/fdselector.h
@@ -0,0 +1,215 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+
+/**
+ * Interface used to listen for events from a @ref FNET_FDSelector io
+ * component.
+ **/
+class FNET_IFDSelectorHandler
+{
+public:
+ /**
+ * This method is called by the transport thread when a read is
+ * possible on the underlying filedescriptor of the given
+ * selector.
+ *
+ * @param source the source of this event
+ **/
+ virtual void readEvent(FNET_FDSelector *source) = 0;
+
+ /**
+ * This method is called by the transport thread when a write is
+ * possible on the underlying filedescriptor of the given
+ * selector.
+ *
+ * @param source the source of this event
+ **/
+ virtual void writeEvent(FNET_FDSelector *source) = 0;
+
+protected:
+ /**
+ * Empty. Implemented just to keep gcc happy. Protected to avoid
+ * destruction through interface pointer.
+ **/
+ virtual ~FNET_IFDSelectorHandler() {}
+};
+
+
+/**
+ * This is an adapter class used to wait for read/write events on a
+ * generic file descriptor. The file descriptor is owned by the
+ * application, and no other operations than checking for read/write
+ * availability will be performed on it. Objects of this class will be
+ * hooked into the io component framework of FNET, and will therefore
+ * have a lifetime controlled by reference counting. The way to use
+ * this class is to pair it up with the @ref FNET_IFDSelectorHandler
+ * interface. Let an object in the application inherit from the
+ * selector handler interface. Create an fd selector using the
+ * application handler. Appropriate events will be delivered to the
+ * selector handler. When no more events are wanted, use the @ref
+ * dispose method to get rid of the selector.
+ **/
+class FNET_FDSelector : public FNET_IOComponent
+{
+ FNET_FDSelector(const FNET_FDSelector&); // not used
+ FNET_FDSelector& operator= (const FNET_FDSelector&); // not used
+public:
+#ifndef IAM_DOXYGEN
+ class FDSpec
+ {
+ private:
+ char _buf[64];
+ public:
+ FDSpec(int fd) : _buf() { sprintf(_buf, "fd/%d", fd); }
+ const char *spec() const { return _buf; }
+ };
+
+ class FDSocket : public FastOS_Socket
+ {
+ public:
+ FDSocket(int fd) : FastOS_Socket() { _socketHandle = fd; }
+ bool valid() const { return _socketHandle != -1; }
+ ~FDSocket() { _socketHandle = -1; }
+ };
+#endif // DOXYGEN
+
+private:
+ int _fd;
+ FDSocket _fdSocket;
+ FNET_IFDSelectorHandler *_handler;
+ FNET_Context _context;
+ bool _eventBusy;
+ bool _eventWait;
+
+ /**
+ * If an event is being delivered, wait until that event is
+ * delivered.
+ **/
+ void waitEvent()
+ {
+ while (_eventBusy) {
+ _eventWait = true;
+ Wait();
+ }
+ }
+
+ /**
+ * Called directly before an event is delivered to synchronize
+ * with the @ref waitEvent method.
+ **/
+ void beforeEvent()
+ {
+ _eventBusy = true;
+ Unlock();
+ }
+
+ /**
+ * Called directly after an event is delivered to synchronize with
+ * the @ref waitEvent method.
+ **/
+ void afterEvent()
+ {
+ Lock();
+ _eventBusy = false;
+ if (_eventWait) {
+ _eventWait = false;
+ Broadcast();
+ }
+ }
+
+public:
+ /**
+ * Construct a file descriptor selector. The created selector is
+ * automatically added to one of the event loops controlled by the
+ * transport object.
+ *
+ * @param transport the transport layer
+ * @param fd the underlying file descriptor
+ * @param handler the handler for this selector
+ * @param context the application context for this selector
+ **/
+ FNET_FDSelector(FNET_Transport *transport, int fd,
+ FNET_IFDSelectorHandler *handler,
+ FNET_Context context = FNET_Context());
+
+ /**
+ * Obtain the file descriptor associated with this selector.
+ *
+ * @return file descriptor
+ **/
+ int getFD() { return _fd; }
+
+ /**
+ * Obtain the application context for this selector.
+ *
+ * @return the application context for this selector
+ **/
+ FNET_Context getContext() { return _context; }
+
+ /**
+ * Enable/disable read events. This method only acts as a proxy
+ * that will notify the transport loop about the new selection.
+ * When a selection is changed it may not take effect right away
+ * (events already in the pipeline will still be delivered). This
+ * means that the application must be able to handle events that
+ * are delivered after events have been disabled.
+ *
+ * @param wantRead true if we want read events.
+ **/
+ void updateReadSelection(bool wantRead);
+
+ /**
+ * Enable/disable write events. This method only acts as a proxy
+ * that will notify the transport loop about the new selection.
+ * When a selection is changed it may not take effect right away
+ * (events already in the pipeline will still be delivered). This
+ * means that the application must be able to handle events that
+ * are delivered after events have been disabled.
+ *
+ * @param wantWrite true if we want write events.
+ **/
+ void updateWriteSelection(bool wantWrite);
+
+ /**
+ * This method is used to dispose of this selector. If an event
+ * callback is in progress when this method is called, it will
+ * block until that callback is finished. This ensures that no
+ * more events will be delivered from this selector after this
+ * method has returned. This method also acts as an implicit
+ * SubRef, invalidating the application pointer to this
+ * object. Note: calling this method from either of the event
+ * delivery methods in the selector handler interface will result
+ * in a deadlock, since the calling thread will be waiting for
+ * itself to complete the callback.
+ **/
+ void dispose();
+
+protected:
+ /**
+ * Destructor. Should not be invoked from the application. Use the
+ * @ref dispose method to get rid of selector objects.
+ **/
+ ~FNET_FDSelector();
+
+ /**
+ * This method is called from the transport thread to close this
+ * io component. This method performs internal cleanup related to
+ * the io component framework used in FNET.
+ **/
+ void Close();
+
+ /**
+ * This method is called by the transport thread when the
+ * underlying file descriptor is ready for reading.
+ **/
+ bool HandleReadEvent();
+
+ /**
+ * This method is called by the transport layer when the
+ * underlying file descriptor is ready for writing.
+ **/
+ bool HandleWriteEvent();
+};
+
diff --git a/fnet/src/vespa/fnet/fnet.h b/fnet/src/vespa/fnet/fnet.h
new file mode 100644
index 00000000000..b4ca0d95599
--- /dev/null
+++ b/fnet/src/vespa/fnet/fnet.h
@@ -0,0 +1,142 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/fastos/fastos.h>
+
+// FEATURES
+
+#include "features.h"
+
+// VTAG
+
+#include "vtag.h"
+
+// DEFINES
+
+#define FNET_NOID ((uint32_t)-1)
+
+// THREAD/MUTEX STUFF
+
+#ifdef FASTOS_NO_THREADS
+
+#define FNET_HAS_THREADS false
+
+class FNET_Mutex
+{
+public:
+ FNET_Mutex(const char *, bool) {}
+ void Lock() {}
+ void Unlock() {}
+};
+
+class FNET_Cond : public FNET_Mutex
+{
+ bool Illegal(const char *name) {
+ fprintf(stderr, "FNET_Cond::%s called (FASTOS_NO_THREADS)\n", name);
+ abort();
+ return false;
+ }
+public:
+ FNET_Cond(const char *name, bool leaf)
+ : FNET_Mutex(name, leaf) {}
+ bool TimedWait(int) { return Illegal("TimedWait"); }
+ void Wait() { Illegal("Wait"); }
+ void Signal() { Illegal("Signal"); }
+ void Broadcast() { Illegal("Broadcast"); }
+};
+
+#else // FASTOS_NO_THREADS
+
+#define FNET_HAS_THREADS true
+
+typedef FastOS_Mutex FNET_Mutex;
+typedef FastOS_Cond FNET_Cond;
+
+#endif
+
+// DEPRECATED
+
+#define DEPRECATED __attribute__((deprecated))
+
+// FORWARD DECLARATIONS
+
+class FNET_IPacketFactory;
+class FNET_IPacketHandler;
+class FNET_IPacketStreamer;
+class FNET_IServerAdapter;
+class FNET_IExecutable;
+
+class FNET_Channel;
+class FNET_ChannelLookup;
+class FNET_ChannelPool;
+class FNET_Config;
+class FNET_Connection;
+class FNET_Connector;
+class FNET_Context;
+class FNET_ControlPacket;
+class FNET_DataBuffer;
+class FNET_DummyPacket;
+class FNET_FDSelector;
+class FNET_Info;
+class FNET_IOComponent;
+class FNET_Packet;
+class FNET_PacketQueue;
+class FNET_Scheduler;
+class FNET_SimplePacketStreamer;
+class FNET_StatCounters;
+class FNET_Stats;
+class FNET_Task;
+class FNET_Transport;
+class FNET_TransportThread;
+
+
+// CONTEXT CLASS (union of types)
+#include "context.h"
+
+// INTERFACES
+#include "ipacketfactory.h"
+#include "ipackethandler.h"
+#include "ipacketstreamer.h"
+#include "iserveradapter.h"
+#include "iexecutable.h"
+
+// CLASSES
+#include "task.h"
+#include "scheduler.h"
+#include "config.h"
+#include "stats.h"
+#include "databuffer.h"
+#include "packet.h"
+#include "dummypacket.h"
+#include "controlpacket.h"
+#include "packetqueue.h"
+#include "channel.h"
+#include "channellookup.h"
+#include "simplepacketstreamer.h"
+#include "transport_thread.h"
+#include "iocomponent.h"
+#include "transport.h"
+#include "connection.h"
+#include "connector.h"
+#include "fdselector.h"
+#include "info.h"
+#include "signalshutdown.h"
+
+
+#define ASSERT_OBJECT(pt) \
+ do { \
+ if (pt == NULL || !pt->CheckObject()) { \
+ fprintf(stderr, "%s:%d: ASSERT_OBJECT FAILED!\n", __FILE__, __LINE__); \
+ abort(); \
+ } \
+ } while (false)
+
+#define ASSERT_OBJECT_NOLOCK(pt) \
+ do { \
+ if (pt == NULL || !pt->CheckObject_NoLock()) { \
+ fprintf(stderr, "%s:%d: ASSERT_OBJECT FAILED!\n", __FILE__, __LINE__); \
+ abort(); \
+ } \
+ } while (false)
+
diff --git a/fnet/src/vespa/fnet/frt/.gitignore b/fnet/src/vespa/fnet/frt/.gitignore
new file mode 100644
index 00000000000..583460ae288
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/.gitignore
@@ -0,0 +1,3 @@
+*.So
+.depend
+Makefile
diff --git a/fnet/src/vespa/fnet/frt/CMakeLists.txt b/fnet/src/vespa/fnet/frt/CMakeLists.txt
new file mode 100644
index 00000000000..3b2693797c5
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(fnet_frt OBJECT
+ SOURCES
+ error.cpp
+ invoker.cpp
+ memorytub.cpp
+ packets.cpp
+ reflection.cpp
+ rpcrequest.cpp
+ supervisor.cpp
+ target.cpp
+ values.cpp
+ DEPENDS
+)
diff --git a/fnet/src/vespa/fnet/frt/error.cpp b/fnet/src/vespa/fnet/frt/error.cpp
new file mode 100644
index 00000000000..dc9af221bad
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/error.cpp
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+const char *
+FRT_GetErrorCodeName(uint32_t errorCode)
+{
+ if (errorCode == 0) return "FRTE_NO_ERROR";
+ if (errorCode > 0xffff) return "[APPLICATION ERROR]";
+
+ if (errorCode >= FRTE_RPC_FIRST &&
+ errorCode <= FRTE_RPC_LAST)
+ {
+ switch (errorCode) {
+ case FRTE_RPC_GENERAL_ERROR: return "FRTE_RPC_GENERAL_ERROR";
+ case FRTE_RPC_NOT_IMPLEMENTED: return "FRTE_RPC_NOT_IMPLEMENTED";
+ case FRTE_RPC_ABORT: return "FRTE_RPC_ABORT";
+ case FRTE_RPC_TIMEOUT: return "FRTE_RPC_TIMEOUT";
+ case FRTE_RPC_CONNECTION: return "FRTE_RPC_CONNECTION";
+ case FRTE_RPC_BAD_REQUEST: return "FRTE_RPC_BAD_REQUEST";
+ case FRTE_RPC_NO_SUCH_METHOD: return "FRTE_RPC_NO_SUCH_METHOD";
+ case FRTE_RPC_WRONG_PARAMS: return "FRTE_RPC_WRONG_PARAMS";
+ case FRTE_RPC_OVERLOAD: return "FRTE_RPC_OVERLOAD";
+ case FRTE_RPC_WRONG_RETURN: return "FRTE_RPC_WRONG_RETURN";
+ case FRTE_RPC_BAD_REPLY: return "FRTE_RPC_BAD_REPLY";
+ case FRTE_RPC_METHOD_FAILED: return "FRTE_RPC_METHOD_FAILED";
+ default: return "[UNKNOWN RPC ERROR]";
+ }
+ }
+ return "[UNKNOWN ERROR]";
+}
+
+
+const char *
+FRT_GetDefaultErrorMessage(uint32_t errorCode)
+{
+ if (errorCode == 0) return "No error";
+ if (errorCode > 0xffff) return "[APPLICATION ERROR]";
+
+ if (errorCode >= FRTE_RPC_FIRST &&
+ errorCode <= FRTE_RPC_LAST)
+ {
+ switch (errorCode) {
+ case FRTE_RPC_GENERAL_ERROR: return "(RPC) General error";
+ case FRTE_RPC_NOT_IMPLEMENTED: return "(RPC) Not implemented";
+ case FRTE_RPC_ABORT: return "(RPC) Invocation aborted";
+ case FRTE_RPC_TIMEOUT: return "(RPC) Invocation timed out";
+ case FRTE_RPC_CONNECTION: return "(RPC) Connection error";
+ case FRTE_RPC_BAD_REQUEST: return "(RPC) Bad request packet";
+ case FRTE_RPC_NO_SUCH_METHOD: return "(RPC) No such method";
+ case FRTE_RPC_WRONG_PARAMS: return "(RPC) Illegal parameters";
+ case FRTE_RPC_OVERLOAD: return "(RPC) Request dropped due to server overload";
+ case FRTE_RPC_WRONG_RETURN: return "(RPC) Illegal return values";
+ case FRTE_RPC_BAD_REPLY: return "(RPC) Bad reply packet";
+ case FRTE_RPC_METHOD_FAILED: return "(RPC) Method failed";
+ default: return "[UNKNOWN RPC ERROR]";
+ }
+ }
+ return "[UNKNOWN ERROR]";
+}
diff --git a/fnet/src/vespa/fnet/frt/error.h b/fnet/src/vespa/fnet/frt/error.h
new file mode 100644
index 00000000000..994e303ae33
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/error.h
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+enum {
+ FRTE_NO_ERROR = 0,
+ FRTE_RPC_FIRST = 100,
+ FRTE_RPC_GENERAL_ERROR = 100,
+ FRTE_RPC_NOT_IMPLEMENTED = 101,
+ FRTE_RPC_ABORT = 102,
+ FRTE_RPC_TIMEOUT = 103,
+ FRTE_RPC_CONNECTION = 104,
+ FRTE_RPC_BAD_REQUEST = 105,
+ FRTE_RPC_NO_SUCH_METHOD = 106,
+ FRTE_RPC_WRONG_PARAMS = 107,
+ FRTE_RPC_OVERLOAD = 108,
+ FRTE_RPC_WRONG_RETURN = 109,
+ FRTE_RPC_BAD_REPLY = 110,
+ FRTE_RPC_METHOD_FAILED = 111,
+ FRTE_RPC_LAST = 199
+};
+
+const char *FRT_GetErrorCodeName(uint32_t errorCode);
+const char *FRT_GetDefaultErrorMessage(uint32_t errorCode);
+
diff --git a/fnet/src/vespa/fnet/frt/frt.h b/fnet/src/vespa/fnet/frt/frt.h
new file mode 100644
index 00000000000..9c8441e4a1d
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/frt.h
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+class FRT_Invokable;
+class FRT_IAbortHandler;
+class FRT_IReturnHandler;
+class FRT_ICleanupHandler;
+class FRT_ISharedBlob;
+
+class FRT_MemoryTub;
+class FRT_Method;
+class FRT_PacketFactory;
+class FRT_ReflectionBuilder;
+class FRT_ReflectionManager;
+class FRT_RPCErrorPacket;
+class FRT_RPCInvoker;
+class FRT_RPCReplyPacket;
+class FRT_RPCRequest;
+class FRT_RPCRequestPacket;
+class FRT_Supervisor;
+class FRT_Target;
+class FRT_Values;
+
+#include <vespa/fnet/fnet.h>
+#include "error.h"
+#include "isharedblob.h"
+#include "invokable.h"
+#include "memorytub.h"
+#include "values.h"
+#include "reflection.h"
+#include "rpcrequest.h"
+#include "packets.h"
+#include "invoker.h"
+#include "supervisor.h"
+#include "target.h"
+
diff --git a/fnet/src/vespa/fnet/frt/invokable.h b/fnet/src/vespa/fnet/frt/invokable.h
new file mode 100644
index 00000000000..31112e5934d
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/invokable.h
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+class FRT_Invokable
+{
+public:
+ virtual ~FRT_Invokable() {}
+};
+
+typedef void (FRT_Invokable::*FRT_METHOD_PT)(FRT_RPCRequest *);
+
+#define FRT_METHOD(pt) ((FRT_METHOD_PT) &pt)
+
diff --git a/fnet/src/vespa/fnet/frt/invoker.cpp b/fnet/src/vespa/fnet/frt/invoker.cpp
new file mode 100644
index 00000000000..6124bc4ed42
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/invoker.cpp
@@ -0,0 +1,189 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet.frt.invoker");
+#include <vespa/fnet/frt/frt.h>
+
+
+void
+FRT_SingleReqWait::RequestDone(FRT_RPCRequest *req)
+{
+ (void) req;
+ _cond.Lock();
+ _done = true;
+ if (_waiting)
+ _cond.Signal();
+ _cond.Unlock();
+}
+
+
+FRT_RPCInvoker::FRT_RPCInvoker(FRT_Supervisor *supervisor,
+ FRT_RPCRequest *req,
+ bool noReply)
+ : _req(req),
+ _method(supervisor->GetReflectionManager()
+ ->LookupMethod(req->GetMethodName())),
+ _noReply(noReply)
+{
+ if (LOG_WOULD_LOG(debug)) {
+ std::string methodName(_req->GetMethodName(), _req->GetMethodNameLen());
+ LOG(debug, "invoke(server) init: '%s'", methodName.c_str());
+ }
+ if (_method == NULL) {
+ if (!req->IsError()) { // may be BAD_REQUEST
+ req->SetError(FRTE_RPC_NO_SUCH_METHOD);
+ }
+ } else if (!FRT_Values::CheckTypes(_method->GetParamSpec(),
+ req->GetParamSpec()))
+ {
+ req->SetError(FRTE_RPC_WRONG_PARAMS);
+ }
+ req->SetReturnHandler(this);
+}
+
+void
+FRT_RPCInvoker::HandleDone(bool freeChannel)
+{
+ FNET_Channel *ch = _req->GetContext()._value.CHANNEL;
+
+ // check return value(s)
+ if (!_req->IsError() &&
+ !FRT_Values::CheckTypes(_method->GetReturnSpec(),
+ _req->GetReturnSpec()))
+ {
+ _req->SetError(FRTE_RPC_WRONG_RETURN);
+ }
+ if (LOG_WOULD_LOG(debug)) {
+ std::string methodName(_req->GetMethodName(), _req->GetMethodNameLen());
+ LOG(debug, "invoke(server) done: '%s': '%s'",
+ methodName.c_str(), FRT_GetErrorCodeName(_req->GetErrorCode()));
+ }
+ // send response to client or get rid of it
+ if (_noReply || (_req->GetErrorCode() == FRTE_RPC_BAD_REQUEST))
+ _req->SubRef();
+ else
+ ch->Send(_req->CreateReplyPacket());
+
+ // free FNET channel (if not in packet delivery callback)
+ if (freeChannel)
+ ch->Free();
+}
+
+void
+FRT_RPCInvoker::HandleReturn()
+{
+ HandleDone(true);
+}
+
+
+FNET_Connection *
+FRT_RPCInvoker::GetConnection()
+{
+ return _req->GetContext()._value.CHANNEL->GetConnection();
+}
+
+
+void
+FRT_RPCInvoker::Run(FastOS_ThreadInterface *, void *)
+{
+ Invoke(true);
+}
+
+//-----------------------------------------------------------------------------
+
+void
+FRT_HookInvoker::HandleReturn()
+{
+ // hooks cannot be detached
+ abort();
+}
+
+
+FNET_Connection *
+FRT_HookInvoker::GetConnection()
+{
+ return _conn;
+}
+
+//-----------------------------------------------------------------------------
+
+FRT_RPCAdapter::FRT_RPCAdapter(FNET_Scheduler *scheduler,
+ FRT_RPCRequest *req,
+ FRT_IRequestWait *waiter)
+ : FNET_Task(scheduler),
+ _req(req),
+ _waiter(waiter),
+ _channel(NULL)
+{
+ if (LOG_WOULD_LOG(debug)) {
+ std::string methodName(_req->GetMethodName(), _req->GetMethodNameLen());
+ LOG(debug, "invoke(client) init: '%s'", methodName.c_str());
+ }
+ req->SetAbortHandler(this);
+}
+
+void
+FRT_RPCAdapter::HandleDone()
+{
+ if (LOG_WOULD_LOG(debug)) {
+ std::string methodName(_req->GetMethodName(), _req->GetMethodNameLen());
+ LOG(debug, "invoke(client) done: '%s': '%s'",
+ methodName.c_str(), FRT_GetErrorCodeName(_req->GetErrorCode()));
+ }
+ // give req back to caller
+ _waiter->RequestDone(_req);
+}
+
+bool
+FRT_RPCAdapter::HandleAbort()
+{
+ if (!_req->GetCompletionToken()) { // too late
+ return false;
+ }
+ if (_channel != NULL) {
+ _channel->CloseAndFree();
+ }
+ Kill();
+ _req->SetError(FRTE_RPC_ABORT);
+ HandleDone();
+ return true;
+}
+
+
+void
+FRT_RPCAdapter::PerformTask()
+{
+ if (!_req->GetCompletionToken()) { // too late
+ return;
+ }
+ if (_channel != NULL) {
+ _channel->CloseAndFree();
+ }
+ if (!_req->IsError()) {
+ _req->SetError(FRTE_RPC_TIMEOUT);
+ }
+ HandleDone();
+}
+
+
+FNET_IPacketHandler::HP_RetCode
+FRT_RPCAdapter::HandlePacket(FNET_Packet *packet, FNET_Context)
+{
+ if (!_req->GetCompletionToken()) { // too late
+ packet->Free();
+ return FNET_KEEP_CHANNEL;
+ }
+ Kill();
+ if (!packet->IsRegularPacket()) {
+ if (packet->IsChannelLostCMD()) {
+ _req->SetError(FRTE_RPC_CONNECTION);
+ }
+ if (packet->IsBadPacketCMD()) {
+ _req->SetError(FRTE_RPC_BAD_REPLY);
+ }
+ }
+ packet->Free();
+ HandleDone();
+ return FNET_FREE_CHANNEL;
+}
diff --git a/fnet/src/vespa/fnet/frt/invoker.h b/fnet/src/vespa/fnet/frt/invoker.h
new file mode 100644
index 00000000000..060397604ee
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/invoker.h
@@ -0,0 +1,168 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+//-----------------------------------------------------------------------------
+
+class FRT_IRequestWait
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FRT_IRequestWait(void) {}
+
+ virtual void RequestDone(FRT_RPCRequest *req) = 0;
+};
+
+//-----------------------------------------------------------------------------
+
+class FRT_SingleReqWait : public FRT_IRequestWait
+{
+private:
+ FNET_Cond _cond;
+ bool _done;
+ bool _waiting;
+
+public:
+ FRT_SingleReqWait()
+ : _cond(),
+ _done(false),
+ _waiting(false) {}
+ virtual ~FRT_SingleReqWait() {}
+
+ void WaitReq()
+ {
+ _cond.Lock();
+ _waiting = true;
+ while(!_done)
+ _cond.Wait();
+ _waiting = false;
+ _cond.Unlock();
+ }
+
+ virtual void RequestDone(FRT_RPCRequest *req);
+};
+
+//-----------------------------------------------------------------------------
+
+class FRT_ITimeoutHandler
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FRT_ITimeoutHandler(void) {}
+
+ virtual void HandleTimeout() = 0;
+};
+
+//-----------------------------------------------------------------------------
+
+class FRT_RPCInvoker : public FastOS_Runnable,
+ public FRT_IReturnHandler
+{
+private:
+ FRT_RPCRequest *_req;
+ FRT_Method *_method;
+ bool _noReply;
+
+ FRT_RPCInvoker(const FRT_RPCInvoker &);
+ FRT_RPCInvoker &operator=(const FRT_RPCInvoker &);
+
+public:
+ FRT_RPCInvoker(FRT_Supervisor *supervisor,
+ FRT_RPCRequest *req,
+ bool noReply);
+
+ void ForceMethod(FRT_Method *method) { _method = method; }
+ bool IsInstant() { return _method->IsInstant(); }
+
+ FRT_RPCRequest *GetRequest() { return _req; }
+
+ void HandleDone(bool freeChannel);
+
+ bool Invoke(bool freeChannel)
+ {
+ bool detached = false;
+ _req->SetDetachedPT(&detached);
+ (_method->GetHandler()->*_method->GetMethod())(_req);
+ if (detached)
+ return false;
+ HandleDone(freeChannel);
+ return true;
+ }
+
+ virtual void HandleReturn();
+ virtual FNET_Connection *GetConnection();
+ virtual void Run(FastOS_ThreadInterface *, void *);
+};
+
+//-----------------------------------------------------------------------------
+
+class FRT_HookInvoker : public FRT_IReturnHandler
+{
+private:
+ FRT_RPCRequest *_req;
+ FRT_Method *_hook;
+ FNET_Connection *_conn;
+
+ FRT_HookInvoker(const FRT_HookInvoker &);
+ FRT_HookInvoker &operator=(const FRT_HookInvoker &);
+
+public:
+ FRT_HookInvoker(FRT_RPCRequest *req,
+ FRT_Method *hook,
+ FNET_Connection *conn)
+ : _req(req),
+ _hook(hook),
+ _conn(conn)
+ {
+ _req->SetReturnHandler(this);
+ }
+
+ void Invoke()
+ {
+ bool detached = false;
+ _req->SetDetachedPT(&detached);
+ (_hook->GetHandler()->*_hook->GetMethod())(_req);
+ assert(!detached);
+ _req->SubRef();
+ }
+
+ virtual void HandleReturn();
+ virtual FNET_Connection *GetConnection();
+};
+
+//-----------------------------------------------------------------------------
+
+class FRT_RPCAdapter : public FNET_Task,
+ public FRT_IAbortHandler,
+ public FNET_IPacketHandler
+{
+private:
+ FRT_RPCRequest *_req;
+ FRT_IRequestWait *_waiter;
+ FNET_Channel *_channel;
+
+ FRT_RPCAdapter(const FRT_RPCAdapter &);
+ FRT_RPCAdapter &operator=(const FRT_RPCAdapter &);
+
+public:
+ FRT_RPCAdapter(FNET_Scheduler *scheduler,
+ FRT_RPCRequest *req,
+ FRT_IRequestWait *waiter);
+
+ void SetChannel(FNET_Channel *channel) { _channel = channel; }
+
+ void HandleDone();
+
+ virtual bool HandleAbort();
+ virtual void PerformTask();
+ virtual HP_RetCode HandlePacket(FNET_Packet *packet, FNET_Context context);
+};
+
+//-----------------------------------------------------------------------------
+
diff --git a/fnet/src/vespa/fnet/frt/isharedblob.h b/fnet/src/vespa/fnet/frt/isharedblob.h
new file mode 100644
index 00000000000..f75122b8c48
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/isharedblob.h
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+
+class FRT_ISharedBlob
+{
+public:
+ virtual void addRef() = 0;
+ virtual void subRef() = 0;
+ virtual uint32_t getLen() = 0;
+ virtual const char *getData() = 0;
+protected:
+ virtual ~FRT_ISharedBlob() {}
+};
+
+
diff --git a/fnet/src/vespa/fnet/frt/memorytub.cpp b/fnet/src/vespa/fnet/frt/memorytub.cpp
new file mode 100644
index 00000000000..736c6572608
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/memorytub.cpp
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+void *
+FRT_MemoryTub::SlowAlloc(size_t size)
+{
+ void *chunkMem = malloc(sizeof(Chunk) + CHUNK_SIZE);
+ assert(chunkMem != NULL);
+ _chunkHead = new (chunkMem) Chunk(CHUNK_SIZE, 0, _chunkHead);
+ return _chunkHead->Alloc(size);
+}
+
+
+void *
+FRT_MemoryTub::BigAlloc(size_t size)
+{
+ void *ret = malloc(size);
+ assert(ret != NULL);
+ _allocHead = new (this) AllocInfo(_allocHead, size, ret);
+ return ret;
+}
+
+
+bool
+FRT_MemoryTub::InTub(const void *pt) const
+{
+ const char *p = (const char *) pt;
+
+ for (Chunk *chunk = _chunkHead; chunk != NULL; chunk = chunk->_next)
+ if (p >= chunk->_data &&
+ p < chunk->_data + chunk->_used)
+ return true;
+
+ for (AllocInfo *info = _allocHead; info != NULL; info = info->_next)
+ if (p >= (char *) info->_data &&
+ p < (char *) info->_data + info->_size)
+ return true;
+
+ return false;
+}
diff --git a/fnet/src/vespa/fnet/frt/memorytub.h b/fnet/src/vespa/fnet/frt/memorytub.h
new file mode 100644
index 00000000000..3ac27a5079a
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/memorytub.h
@@ -0,0 +1,147 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <new>
+
+class FRT_MemoryTub
+{
+
+public:
+
+ enum {
+ CHUNK_SIZE = 32500,
+ FIXED_SIZE = 3880,
+ ALLOC_LIMIT = 3200
+ };
+
+ struct AllocInfo {
+ AllocInfo *_next;
+ uint32_t _size;
+ void *_data;
+
+ AllocInfo(AllocInfo *next, uint32_t size, void *data)
+ : _next(next), _size(size), _data(data) {}
+
+ private:
+ AllocInfo(const AllocInfo &);
+ AllocInfo &operator=(const AllocInfo &);
+ };
+
+ struct Chunk {
+ uint32_t _size;
+ uint32_t _used;
+ Chunk *_next;
+ char *_data;
+
+ Chunk(uint32_t size, uint32_t used, Chunk *next)
+ : _size(size), _used(used), _next(next), _data(NULL)
+ {
+ _data = reinterpret_cast<char*>(this + 1);
+ }
+
+ void *Alloc(size_t size)
+ {
+ size_t alignedsize = ((size + (sizeof(char *) - 1))
+ & ~(sizeof(char *) - 1));
+ if (_used + alignedsize <= _size) {
+ void *ret = _data + _used;
+ _used += alignedsize;
+ return ret;
+ }
+ return NULL;
+ }
+
+ private:
+ Chunk(const Chunk &);
+ Chunk &operator=(const Chunk &);
+ };
+
+private:
+
+ Chunk _fixedChunk;
+ char _fixedData[FIXED_SIZE];
+ Chunk *_chunkHead;
+ AllocInfo *_allocHead;
+
+ FRT_MemoryTub(const FRT_MemoryTub &);
+ FRT_MemoryTub &operator=(const FRT_MemoryTub &);
+
+ void *SlowAlloc(size_t size);
+ void *BigAlloc(size_t size);
+
+public:
+
+ FRT_MemoryTub()
+ : _fixedChunk(FIXED_SIZE, 0, NULL),
+ _chunkHead(&_fixedChunk),
+ _allocHead(NULL)
+ {
+ // Just to be sure
+ _fixedChunk._data = _fixedData;
+ }
+
+ bool InTub(const void *pt) const;
+
+ void *Alloc(size_t size)
+ {
+ if (size > ALLOC_LIMIT)
+ return BigAlloc(size);
+ void *tmp = _chunkHead->Alloc(size);
+ return (tmp != NULL) ? tmp : SlowAlloc(size);
+ };
+
+ char *CopyString(const char *str, uint32_t len)
+ {
+ char *pt = (char *) Alloc(len + 1);
+ memcpy(pt, str, len);
+ pt[len] = '\0';
+ return pt;
+ }
+
+ char *CopyData(const char *buf, uint32_t len)
+ {
+ char *pt = (char *) Alloc(len);
+ memcpy(pt, buf, len);
+ return pt;
+ }
+
+ void Reset()
+ {
+ for (AllocInfo *info = _allocHead;
+ info != NULL; info = info->_next) {
+ free(info->_data);
+ }
+ _allocHead = NULL;
+ while (_chunkHead != &_fixedChunk) {
+ Chunk *tmp = _chunkHead;
+ _chunkHead = tmp->_next;
+ free(tmp);
+ }
+ _fixedChunk._used = 0;
+ }
+
+ ~FRT_MemoryTub()
+ {
+ Reset();
+ }
+};
+
+
+inline void *
+operator new(size_t size, FRT_MemoryTub *tub)
+{
+ return tub->Alloc(size);
+}
+
+
+inline void *
+operator new[](size_t size, FRT_MemoryTub *tub)
+{
+ (void) size;
+ (void) tub;
+ fprintf(stderr, "Microsoft does not permit this operation!");
+ abort();
+ return malloc(size);
+}
+
diff --git a/fnet/src/vespa/fnet/frt/packets.cpp b/fnet/src/vespa/fnet/frt/packets.cpp
new file mode 100644
index 00000000000..964293fae4f
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/packets.cpp
@@ -0,0 +1,271 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+
+FRT_RPCPacket::~FRT_RPCPacket()
+{
+ assert(false);
+}
+
+
+void
+FRT_RPCPacket::Free()
+{
+ if (_ownsRef) {
+ _req->DiscardBlobs();
+ _req->SubRef();
+ }
+}
+
+//--------------------------------------------------------------------
+
+uint32_t
+FRT_RPCRequestPacket::GetPCODE()
+{
+ return (_flags << 16) + PCODE_FRT_RPC_REQUEST;
+}
+
+
+uint32_t
+FRT_RPCRequestPacket::GetLength()
+{
+ return (sizeof(uint32_t)
+ + _req->GetMethodNameLen()
+ + _req->GetParams()->GetLength());
+}
+
+
+void
+FRT_RPCRequestPacket::Encode(FNET_DataBuffer *dst)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t host_endian = FNET_Info::GetEndian();
+
+ if (packet_endian == host_endian) {
+ uint32_t tmp = _req->GetMethodNameLen();
+ dst->WriteBytesFast(&tmp, sizeof(tmp));
+ dst->WriteBytesFast(_req->GetMethodName(),
+ _req->GetMethodNameLen());
+ _req->GetParams()->EncodeCopy(dst);
+ } else {
+ assert(packet_endian == FNET_Info::ENDIAN_BIG);
+ dst->WriteInt32Fast(_req->GetMethodNameLen());
+ dst->WriteBytesFast(_req->GetMethodName(),
+ _req->GetMethodNameLen());
+ _req->GetParams()->EncodeBig(dst);
+ }
+}
+
+
+bool
+FRT_RPCRequestPacket::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t host_endian = FNET_Info::GetEndian();
+ uint32_t slen;
+
+ if (len < sizeof(uint32_t)) goto error;
+ slen = (packet_endian == FNET_Info::ENDIAN_BIG)
+ ? src->ReadInt32() : src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < slen) goto error;
+ _req->SetMethodName(src->GetData(), slen);
+ src->DataToDead(slen);
+ len -= slen;
+
+ return (packet_endian == host_endian)
+ ? _req->GetParams()->DecodeCopy(src, len)
+ : ((packet_endian == FNET_Info::ENDIAN_BIG)
+ ? _req->GetParams()->DecodeBig(src, len)
+ : _req->GetParams()->DecodeLittle(src, len));
+
+error:
+ src->DataToDead(len);
+ return false; // FAIL
+}
+
+
+vespalib::string
+FRT_RPCRequestPacket::Print(uint32_t indent)
+{
+ vespalib::string s;
+ s += vespalib::make_string("%*sFRT_RPCRequestPacket {\n", indent, "");
+ s += vespalib::make_string("%*s method name: %s\n", indent, "",
+ (_req->GetMethodName() != NULL)
+ ? _req->GetMethodName() : "N/A");
+ s += vespalib::make_string("%*s params:\n", indent, "");
+ _req->GetParams()->Print(indent + 2);
+ s += vespalib::make_string("%*s}\n", indent, "");
+ return s;
+}
+
+//--------------------------------------------------------------------
+
+uint32_t
+FRT_RPCReplyPacket::GetPCODE()
+{
+ return (_flags << 16) + PCODE_FRT_RPC_REPLY;
+}
+
+
+uint32_t
+FRT_RPCReplyPacket::GetLength()
+{
+ return _req->GetReturn()->GetLength();
+}
+
+
+void
+FRT_RPCReplyPacket::Encode(FNET_DataBuffer *dst)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t host_endian = FNET_Info::GetEndian();
+
+ if (packet_endian == host_endian) {
+ _req->GetReturn()->EncodeCopy(dst);
+ } else {
+ assert(packet_endian == FNET_Info::ENDIAN_BIG);
+ _req->GetReturn()->EncodeBig(dst);
+ }
+}
+
+
+bool
+FRT_RPCReplyPacket::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t host_endian = FNET_Info::GetEndian();
+
+ return (packet_endian == host_endian)
+ ? _req->GetReturn()->DecodeCopy(src, len)
+ : ((packet_endian == FNET_Info::ENDIAN_BIG)
+ ? _req->GetReturn()->DecodeBig(src, len)
+ : _req->GetReturn()->DecodeLittle(src, len));
+}
+
+
+vespalib::string
+FRT_RPCReplyPacket::Print(uint32_t indent)
+{
+ vespalib::string s;
+ s += vespalib::make_string("%*sFRT_RPCReplyPacket {\n", indent, "");
+ s += vespalib::make_string("%*s return:\n", indent, "");
+ _req->GetReturn()->Print(indent + 2);
+ s += vespalib::make_string("%*s}\n", indent, "");
+ return s;
+}
+
+//--------------------------------------------------------------------
+
+uint32_t
+FRT_RPCErrorPacket::GetPCODE()
+{
+ return (_flags << 16) + PCODE_FRT_RPC_ERROR;
+}
+
+
+uint32_t
+FRT_RPCErrorPacket::GetLength()
+{
+ return sizeof(uint32_t) * 2 + _req->GetErrorMessageLen();
+}
+
+
+void
+FRT_RPCErrorPacket::Encode(FNET_DataBuffer *dst)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t host_endian = FNET_Info::GetEndian();
+
+ if (packet_endian == host_endian) {
+ uint32_t tmp = _req->GetErrorCode();
+ dst->WriteBytesFast(&tmp, sizeof(tmp));
+ tmp = _req->GetErrorMessageLen();
+ dst->WriteBytesFast(&tmp, sizeof(tmp));
+ dst->WriteBytesFast(_req->GetErrorMessage(),
+ _req->GetErrorMessageLen());
+ } else {
+ assert(packet_endian == FNET_Info::ENDIAN_BIG);
+ dst->WriteInt32Fast(_req->GetErrorCode());
+ dst->WriteInt32Fast(_req->GetErrorMessageLen());
+ dst->WriteBytesFast(_req->GetErrorMessage(),
+ _req->GetErrorMessageLen());
+ }
+}
+
+
+bool
+FRT_RPCErrorPacket::Decode(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t packet_endian = ((_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0)
+ ? FNET_Info::ENDIAN_LITTLE : FNET_Info::ENDIAN_BIG;
+ uint32_t errorCode;
+ uint32_t errorMsgLen;
+
+ if (len < 2 * sizeof(uint32_t)) goto error;
+ errorCode = (packet_endian == FNET_Info::ENDIAN_BIG)
+ ? src->ReadInt32() : src->ReadInt32Reverse();
+ errorMsgLen = (packet_endian == FNET_Info::ENDIAN_BIG)
+ ? src->ReadInt32() : src->ReadInt32Reverse();
+ len -= 2 * sizeof(uint32_t);
+ if (len < errorMsgLen) goto error;
+ _req->SetError(errorCode, src->GetData(), errorMsgLen);
+ src->DataToDead(errorMsgLen);
+ len -= errorMsgLen;
+ if (len != 0) goto error;
+ return true; // OK
+
+error:
+ src->DataToDead(len);
+ return false; // FAIL
+}
+
+
+vespalib::string
+FRT_RPCErrorPacket::Print(uint32_t indent)
+{
+ vespalib::string s;
+ s += vespalib::make_string("%*sFRT_RPCErrorPacket {\n", indent, "");
+ s += vespalib::make_string("%*s error code : %d\n", indent, "", _req->GetErrorCode());
+ s += vespalib::make_string("%*s error message: %s\n", indent, "",
+ (_req->GetErrorMessage() != NULL)
+ ? _req->GetErrorMessage() : "N/A");
+ s += vespalib::make_string("%*s}\n", indent, "");
+ return s;
+}
+
+//--------------------------------------------------------------------
+
+FNET_Packet *
+FRT_PacketFactory::CreatePacket(uint32_t pcode, FNET_Context context)
+{
+ FRT_RPCRequest *req = ((FRT_RPCRequest *)context._value.VOIDP);
+ uint32_t flags = (pcode >> 16) & 0xffff;
+
+ if (req == NULL || (flags & ~FLAG_FRT_RPC_SUPPORTED_MASK) != 0)
+ return NULL;
+
+ FRT_MemoryTub *tub = req->GetMemoryTub();
+ pcode &= 0xffff; // remove flags
+
+ switch(pcode) {
+
+ case PCODE_FRT_RPC_REQUEST:
+ return new (tub) FRT_RPCRequestPacket(req, flags, false);
+
+ case PCODE_FRT_RPC_REPLY:
+ return new (tub) FRT_RPCReplyPacket(req, flags, false);
+
+ case PCODE_FRT_RPC_ERROR:
+ return new (tub) FRT_RPCErrorPacket(req, flags, false);
+ }
+ return NULL;
+}
diff --git a/fnet/src/vespa/fnet/frt/packets.h b/fnet/src/vespa/fnet/frt/packets.h
new file mode 100644
index 00000000000..e52baaa3871
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/packets.h
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+
+enum {
+ PCODE_FRT_RPC_FIRST = 100,
+ PCODE_FRT_RPC_REQUEST = 100,
+ PCODE_FRT_RPC_REPLY = 101,
+ PCODE_FRT_RPC_ERROR = 102,
+ PCODE_FRT_RPC_LAST = 199
+};
+
+
+enum {
+ FLAG_FRT_RPC_LITTLE_ENDIAN = 0x0001,
+ FLAG_FRT_RPC_NOREPLY = 0x0002,
+ FLAG_FRT_RPC_SUPPORTED_MASK = 0x0003
+};
+
+
+class FRT_RPCPacket : public FNET_Packet
+{
+protected:
+ FRT_RPCRequest *_req;
+ uint32_t _flags;
+ bool _ownsRef;
+
+ FRT_RPCPacket(const FRT_RPCPacket &);
+ FRT_RPCPacket &operator=(const FRT_RPCPacket &);
+
+public:
+ FRT_RPCPacket(FRT_RPCRequest *req,
+ uint32_t flags,
+ bool ownsRef)
+ : _req(req),
+ _flags(flags),
+ _ownsRef(ownsRef)
+ {}
+
+ bool LittleEndian() { return (_flags & FLAG_FRT_RPC_LITTLE_ENDIAN) != 0; }
+ bool NoReply() { return (_flags & FLAG_FRT_RPC_NOREPLY) != 0; }
+
+ virtual ~FRT_RPCPacket();
+ virtual void Free();
+};
+
+
+class FRT_RPCRequestPacket : public FRT_RPCPacket
+{
+public:
+ FRT_RPCRequestPacket(FRT_RPCRequest *req,
+ uint32_t flags,
+ bool ownsRef)
+ : FRT_RPCPacket(req, flags, ownsRef) {}
+
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *dst);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
+
+
+class FRT_RPCReplyPacket : public FRT_RPCPacket
+{
+public:
+ FRT_RPCReplyPacket(FRT_RPCRequest *req,
+ uint32_t flags,
+ bool ownsRef)
+ : FRT_RPCPacket(req, flags, ownsRef) {}
+
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *dst);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
+
+
+class FRT_RPCErrorPacket : public FRT_RPCPacket
+{
+public:
+ FRT_RPCErrorPacket(FRT_RPCRequest *req,
+ uint32_t flags,
+ bool ownsRef)
+ : FRT_RPCPacket(req, flags, ownsRef) {}
+
+ virtual uint32_t GetPCODE();
+ virtual uint32_t GetLength();
+ virtual void Encode(FNET_DataBuffer *dst);
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len);
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
+
+
+class FRT_PacketFactory : public FNET_IPacketFactory
+{
+public:
+ FNET_Packet *CreatePacket(uint32_t pcode, FNET_Context context);
+};
+
diff --git a/fnet/src/vespa/fnet/frt/reflection.cpp b/fnet/src/vespa/fnet/frt/reflection.cpp
new file mode 100644
index 00000000000..35e74200709
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/reflection.cpp
@@ -0,0 +1,198 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+
+FRT_ReflectionManager::FRT_ReflectionManager()
+ : _numMethods(0),
+ _methods(NULL),
+ _methodHash()
+{
+ Reset();
+}
+
+
+FRT_ReflectionManager::~FRT_ReflectionManager()
+{
+ Reset();
+}
+
+
+void
+FRT_ReflectionManager::Reset()
+{
+ _numMethods = 0;
+ while (_methods != NULL) {
+ FRT_Method *method = _methods;
+ _methods = method->GetNext();
+ delete(method);
+ }
+
+ for (uint32_t i = 0; i < METHOD_HASH_SIZE; i++)
+ _methodHash[i] = NULL;
+}
+
+
+void
+FRT_ReflectionManager::AddMethod(FRT_Method *method)
+{
+ uint32_t hash = HashStr(method->GetName(), METHOD_HASH_SIZE);
+ method->_hashNext = _methodHash[hash];
+ _methodHash[hash] = method;
+ method->_listNext = _methods;
+ _methods = method;
+ _numMethods++;
+}
+
+
+FRT_Method *
+FRT_ReflectionManager::LookupMethod(const char *name)
+{
+ if (name == NULL) {
+ return NULL;
+ }
+ uint32_t hash = HashStr(name, METHOD_HASH_SIZE);
+ FRT_Method *ret = _methodHash[hash];
+ while (ret != NULL && strcmp(name, ret->GetName()) != 0)
+ ret = ret->_hashNext;
+ return ret;
+}
+
+
+void
+FRT_ReflectionManager::DumpMethodList(FRT_Values *target)
+{
+ FRT_StringValue *names = target->AddStringArray(_numMethods);
+ FRT_StringValue *args = target->AddStringArray(_numMethods);
+ FRT_StringValue *ret = target->AddStringArray(_numMethods);
+ uint32_t idx = 0;
+ for (FRT_Method *method = _methods; method != NULL;
+ method = method->GetNext(), idx++) {
+ target->SetString(&names[idx], method->GetName());
+ target->SetString(&args[idx], method->GetParamSpec());
+ target->SetString(&ret[idx], method->GetReturnSpec());
+ }
+ assert(idx == _numMethods);
+}
+
+//------------------------------------------------------------------------
+
+void
+FRT_ReflectionBuilder::Flush()
+{
+ if (_method == NULL)
+ return;
+
+ for (; _curArg < _argCnt; _curArg++) {
+ _values->SetString(&_arg_name[_curArg], "?");
+ _values->SetString(&_arg_desc[_curArg], "???");
+ }
+ for (; _curRet < _retCnt; _curRet++) {
+ _values->SetString(&_ret_name[_curRet], "?");
+ _values->SetString(&_ret_desc[_curRet], "???");
+ }
+
+ _method->SetDocumentation(_values);
+ _method = NULL;
+ _req->Reset();
+}
+
+
+FRT_ReflectionBuilder::FRT_ReflectionBuilder(FRT_Supervisor *supervisor)
+ : _supervisor(supervisor),
+ _lookup(supervisor->GetReflectionManager()),
+ _method(NULL),
+ _req(supervisor->AllocRPCRequest()),
+ _values(_req->GetReturn()),
+ _argCnt(0),
+ _retCnt(0),
+ _curArg(0),
+ _curRet(0),
+ _arg_name(NULL),
+ _arg_desc(NULL),
+ _ret_name(NULL),
+ _ret_desc(NULL)
+{
+}
+
+
+FRT_ReflectionBuilder::~FRT_ReflectionBuilder()
+{
+ Flush();
+ _req->SubRef();
+}
+
+
+void
+FRT_ReflectionBuilder::DefineMethod(const char *name,
+ const char *paramSpec,
+ const char *returnSpec,
+ bool instant,
+ FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ if (handler == NULL)
+ return;
+
+ Flush();
+ _method = new FRT_Method(name,
+ paramSpec,
+ returnSpec,
+ instant,
+ method,
+ handler);
+ _lookup->AddMethod(_method);
+
+ _argCnt = strlen(paramSpec);
+ _retCnt = strlen(returnSpec);
+ _curArg = 0;
+ _curRet = 0;
+ _values->AddString("???"); // method desc
+ _values->AddString(paramSpec);
+ _values->AddString(returnSpec);
+ _arg_name = _values->AddStringArray(_argCnt);
+ _arg_desc = _values->AddStringArray(_argCnt);
+ _ret_name = _values->AddStringArray(_retCnt);
+ _ret_desc = _values->AddStringArray(_retCnt);
+}
+
+
+void
+FRT_ReflectionBuilder::MethodDesc(const char *desc)
+{
+ if (_method == NULL)
+ return;
+
+ _values->SetString(&_values->GetValue(0)._string, desc);
+}
+
+
+void
+FRT_ReflectionBuilder::ParamDesc(const char *name, const char *desc)
+{
+ if (_method == NULL)
+ return;
+
+ if (_curArg >= _argCnt)
+ return;
+
+ _values->SetString(&_arg_name[_curArg], name);
+ _values->SetString(&_arg_desc[_curArg], desc);
+ _curArg++;
+}
+
+
+void
+FRT_ReflectionBuilder::ReturnDesc(const char *name, const char *desc)
+{
+ if (_method == NULL)
+ return;
+
+ if (_curRet >= _retCnt)
+ return;
+
+ _values->SetString(&_ret_name[_curRet], name);
+ _values->SetString(&_ret_desc[_curRet], desc);
+ _curRet++;
+}
diff --git a/fnet/src/vespa/fnet/frt/reflection.h b/fnet/src/vespa/fnet/frt/reflection.h
new file mode 100644
index 00000000000..4f06d4beb45
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/reflection.h
@@ -0,0 +1,160 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+
+class FRT_Method
+{
+ friend class FRT_ReflectionManager;
+
+private:
+ FRT_Method *_hashNext; // list of methods in hash bucket
+ FRT_Method *_listNext; // list of all methods
+ char *_name; // method name
+ char *_paramSpec; // method parameter spec
+ char *_returnSpec; // method return spec
+ bool _instant; // method is instant ?
+ FRT_METHOD_PT _method; // method pointer
+ FRT_Invokable *_handler; // method handler
+ uint32_t _docLen; // method documentation length
+ char *_doc; // method documentation
+
+ FRT_Method(const FRT_Method &);
+ FRT_Method &operator=(const FRT_Method &);
+
+public:
+ FRT_Method(const char *name,
+ const char *paramSpec,
+ const char *returnSpec,
+ bool instant,
+ FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+ : _hashNext(NULL),
+ _listNext(NULL),
+ _name(strdup(name)),
+ _paramSpec(strdup(paramSpec)),
+ _returnSpec(strdup(returnSpec)),
+ _instant(instant),
+ _method(method),
+ _handler(handler),
+ _docLen(0),
+ _doc(NULL)
+ {
+ assert(_name != NULL);
+ assert(_paramSpec != NULL);
+ assert(_returnSpec != NULL);
+ }
+
+ ~FRT_Method()
+ {
+ free(_name);
+ free(_paramSpec);
+ free(_returnSpec);
+ free(_doc);
+ }
+
+ FRT_Method *GetNext() { return _listNext; }
+ const char *GetName() { return _name; }
+ const char *GetParamSpec() { return _paramSpec; }
+ const char *GetReturnSpec() { return _returnSpec; }
+ bool IsInstant() { return _instant; }
+ FRT_METHOD_PT GetMethod() { return _method; }
+ FRT_Invokable *GetHandler() { return _handler; }
+ void SetDocumentation(FRT_Values *values)
+ {
+ free(_doc);
+ _docLen = values->GetLength();
+ _doc = (char *) malloc(_docLen);
+ assert(_doc != NULL);
+
+ FNET_DataBuffer buf(_doc, _docLen);
+ values->EncodeCopy(&buf);
+ }
+ void GetDocumentation(FRT_Values *values)
+ {
+ FNET_DataBuffer buf(_doc, _docLen);
+ buf.FreeToData(_docLen);
+ values->DecodeCopy(&buf, _docLen);
+ }
+};
+
+//------------------------------------------------------------------------
+
+class FRT_ReflectionManager
+{
+public:
+ enum {
+ METHOD_HASH_SIZE = 6000
+ };
+
+private:
+ uint32_t _numMethods;
+ FRT_Method *_methods;
+ FRT_Method *_methodHash[METHOD_HASH_SIZE];
+
+ FRT_ReflectionManager(const FRT_ReflectionManager &);
+ FRT_ReflectionManager &operator=(const FRT_ReflectionManager &);
+
+ uint32_t HashStr(const char *key, uint32_t width)
+ {
+ uint32_t res = 0;
+ unsigned const char *pt = (unsigned const char *) key;
+ while (*pt != '\0') {
+ res = (res << 7) + (*pt) + (res >> 25);
+ pt++;
+ }
+ return (res % width);
+ }
+
+public:
+ FRT_ReflectionManager();
+ ~FRT_ReflectionManager();
+
+ void Reset();
+ void AddMethod(FRT_Method *method);
+ FRT_Method *LookupMethod(const char *name);
+ void DumpMethodList(FRT_Values *target);
+};
+
+//------------------------------------------------------------------------
+
+class FRT_ReflectionBuilder
+{
+private:
+ FRT_Supervisor *_supervisor;
+ FRT_ReflectionManager *_lookup;
+ FRT_Method *_method;
+
+ // documentation variables below
+
+ FRT_RPCRequest *_req;
+ FRT_Values *_values;
+ uint32_t _argCnt;
+ uint32_t _retCnt;
+ uint32_t _curArg;
+ uint32_t _curRet;
+ FRT_StringValue *_arg_name;
+ FRT_StringValue *_arg_desc;
+ FRT_StringValue *_ret_name;
+ FRT_StringValue *_ret_desc;
+
+ FRT_ReflectionBuilder(const FRT_ReflectionBuilder &);
+ FRT_ReflectionBuilder &operator=(const FRT_ReflectionBuilder &);
+
+ void Flush();
+
+public:
+ FRT_ReflectionBuilder(FRT_Supervisor *supervisor);
+ ~FRT_ReflectionBuilder();
+
+ void DefineMethod(const char *name,
+ const char *paramSpec,
+ const char *returnSpec,
+ bool instant,
+ FRT_METHOD_PT method,
+ FRT_Invokable *handler);
+ void MethodDesc(const char *desc);
+ void ParamDesc(const char *name, const char *desc);
+ void ReturnDesc(const char *name, const char *desc);
+};
+
diff --git a/fnet/src/vespa/fnet/frt/rpcrequest.cpp b/fnet/src/vespa/fnet/frt/rpcrequest.cpp
new file mode 100644
index 00000000000..78789ccd4e9
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/rpcrequest.cpp
@@ -0,0 +1,119 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+FRT_RPCRequest::FRT_RPCRequest()
+ : _tub(),
+ _context(),
+ _params(&_tub),
+ _return(&_tub),
+ _refcnt(1),
+ _completed(0),
+ _errorCode(FRTE_NO_ERROR),
+ _errorMessageLen(0),
+ _methodNameLen(0),
+ _errorMessage(NULL),
+ _methodName(NULL),
+ _detachedPT(NULL),
+ _abortHandler(NULL),
+ _returnHandler(NULL),
+ _cleanupHandler(NULL)
+{
+}
+
+
+FRT_RPCRequest::~FRT_RPCRequest()
+{
+ assert(_refcnt == 0);
+}
+
+
+void
+FRT_RPCRequest::Reset()
+{
+ assert(_refcnt <= 1);
+ Cleanup();
+ _context = FNET_Context();
+ _params.Reset();
+ _return.Reset();
+ _tub.Reset();
+ _errorCode = FRTE_NO_ERROR;
+ _errorMessageLen = 0;
+ _errorMessage = NULL;
+ _methodNameLen = 0;
+ _methodName = NULL;
+ _detachedPT = NULL;
+ _completed = 0;
+ _abortHandler = NULL;
+ _returnHandler = NULL;
+}
+
+
+bool
+FRT_RPCRequest::Recycle()
+{
+ if (_refcnt > 1 || _errorCode != FRTE_NO_ERROR)
+ return false;
+ Reset();
+ return true;
+}
+
+
+void
+FRT_RPCRequest::SubRef()
+{
+ assert(_refcnt > 0);
+ if (vespalib::Atomic::postDec(&_refcnt) == 1) {
+ Reset();
+ delete this;
+ }
+}
+
+
+void
+FRT_RPCRequest::Print(uint32_t indent)
+{
+ printf("%*sFRT_RPCRequest {\n", indent, "");
+ printf("%*s method: %s\n", indent, "",
+ (_methodName != NULL)? _methodName : "(N/A)");
+ printf("%*s error(%d): %s\n", indent, "", _errorCode,
+ (_errorMessage != NULL)
+ ? _errorMessage
+ : FRT_GetDefaultErrorMessage(_errorCode));
+ printf("%*s params:\n", indent, "");
+ _params.Print(indent + 2);
+ printf("%*s return:\n", indent, "");
+ _return.Print(indent + 2);
+ printf("%*s}\n", indent, "");
+}
+
+
+FNET_Packet *
+FRT_RPCRequest::CreateRequestPacket(bool wantReply)
+{
+ uint32_t flags = 0;
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
+ flags |= FLAG_FRT_RPC_LITTLE_ENDIAN;
+
+ if (wantReply)
+ AddRef_NoLock();
+ else
+ flags |= FLAG_FRT_RPC_NOREPLY;
+
+ return new (&_tub) FRT_RPCRequestPacket(this, flags, true);
+}
+
+
+FNET_Packet *
+FRT_RPCRequest::CreateReplyPacket()
+{
+ uint32_t flags = 0;
+ if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
+ flags |= FLAG_FRT_RPC_LITTLE_ENDIAN;
+
+ if (IsError())
+ return new (&_tub) FRT_RPCErrorPacket(this, flags, true);
+ else
+ return new (&_tub) FRT_RPCReplyPacket(this, flags, true);
+}
diff --git a/fnet/src/vespa/fnet/frt/rpcrequest.h b/fnet/src/vespa/fnet/frt/rpcrequest.h
new file mode 100644
index 00000000000..ec0018fa451
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/rpcrequest.h
@@ -0,0 +1,193 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/atomic.h>
+
+
+class FRT_IAbortHandler
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FRT_IAbortHandler(void) {}
+
+ virtual bool HandleAbort() = 0;
+};
+
+
+class FRT_IReturnHandler
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FRT_IReturnHandler(void) {}
+
+ virtual void HandleReturn() = 0;
+ virtual FNET_Connection *GetConnection() = 0;
+};
+
+
+class FRT_ICleanupHandler
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FRT_ICleanupHandler(void) {}
+
+ virtual void HandleCleanup() = 0;
+};
+
+
+class FRT_RPCRequest
+{
+private:
+ FRT_MemoryTub _tub;
+ FNET_Context _context;
+ FRT_Values _params;
+ FRT_Values _return;
+ int _refcnt;
+ int _completed;
+ uint32_t _errorCode;
+ uint32_t _errorMessageLen;
+ uint32_t _methodNameLen;
+ char *_errorMessage;
+ char *_methodName;
+
+ bool *_detachedPT;
+ FRT_IAbortHandler *_abortHandler;
+ FRT_IReturnHandler *_returnHandler;
+ FRT_ICleanupHandler *_cleanupHandler;
+
+ FRT_RPCRequest(const FRT_RPCRequest &);
+ FRT_RPCRequest &operator=(const FRT_RPCRequest &);
+
+public:
+ FRT_RPCRequest();
+ ~FRT_RPCRequest();
+
+ void Reset();
+ bool Recycle();
+
+ void DiscardBlobs()
+ {
+ _params.DiscardBlobs();
+ _return.DiscardBlobs();
+ }
+
+ void AddRef_NoLock() { _refcnt++; } // be very carefull
+ void AddRef() { vespalib::Atomic::postInc(&_refcnt); }
+ void SubRef();
+
+ void SetContext(FNET_Context context) { _context = context; }
+ FNET_Context GetContext() { return _context; }
+
+ FRT_MemoryTub *GetMemoryTub() { return &_tub; }
+
+ FRT_Values *GetParams() { return &_params; }
+ FRT_Values *GetReturn() { return &_return; }
+
+ const char *GetParamSpec()
+ {
+ const char *spec = _params.GetTypeString();
+ return (spec != NULL) ? spec : "";
+ }
+ const char *GetReturnSpec()
+ {
+ const char *spec = _return.GetTypeString();
+ return (spec != NULL) ? spec : "";
+ }
+
+ bool GetCompletionToken() { return (vespalib::Atomic::postInc(&_completed) == 0); }
+
+ void SetError(uint32_t errorCode, const char *errorMessage,
+ uint32_t errorMessageLen)
+ {
+ _errorCode = errorCode;
+ _errorMessageLen = errorMessageLen;
+ _errorMessage = _tub.CopyString(errorMessage,
+ errorMessageLen);
+ }
+ void SetError(uint32_t errorCode, const char *errorMessage)
+ { SetError(errorCode, errorMessage, strlen(errorMessage)); }
+ void SetError(uint32_t errorCode)
+ { SetError(errorCode, FRT_GetDefaultErrorMessage(errorCode)); }
+
+ bool IsError() { return (_errorCode != FRTE_NO_ERROR); }
+ uint32_t GetErrorCode() { return _errorCode; }
+ uint32_t GetErrorMessageLen() { return _errorMessageLen; }
+ const char *GetErrorMessage() { return _errorMessage; }
+
+ bool CheckReturnTypes(const char *types) {
+ if (IsError()) {
+ return false;
+ }
+ if (strcmp(types, GetReturnSpec()) != 0) {
+ SetError(FRTE_RPC_WRONG_RETURN);
+ return false;
+ }
+ return true;
+ }
+
+ void SetMethodName(const char *methodName, uint32_t len)
+ {
+ _methodNameLen = len;
+ _methodName = _tub.CopyString(methodName, len);
+ }
+ void SetMethodName(const char *methodName)
+ { SetMethodName(methodName, strlen(methodName)); }
+
+ uint32_t GetMethodNameLen() { return _methodNameLen; }
+ const char *GetMethodName() { return _methodName; }
+
+ void Print(uint32_t indent = 0);
+
+ FNET_Packet *CreateRequestPacket(bool wantReply);
+ FNET_Packet *CreateReplyPacket();
+
+ void SetDetachedPT(bool *detachedPT) { _detachedPT = detachedPT; }
+ void Detach() { assert(_detachedPT != NULL); *_detachedPT = true; }
+
+ void SetAbortHandler(FRT_IAbortHandler *handler)
+ { _abortHandler = handler; }
+ void SetReturnHandler(FRT_IReturnHandler *handler)
+ { _returnHandler = handler; }
+ void SetCleanupHandler(FRT_ICleanupHandler *handler)
+ { _cleanupHandler = handler; }
+
+ bool Abort()
+ {
+ if (_abortHandler == NULL) {
+ return false;
+ }
+ return _abortHandler->HandleAbort();
+ }
+
+ void Return()
+ {
+ assert(_returnHandler != NULL);
+ _returnHandler->HandleReturn();
+ }
+
+ FNET_Connection *GetConnection()
+ {
+ if (_returnHandler == NULL)
+ return NULL;
+ return _returnHandler->GetConnection();
+ }
+
+ void Cleanup()
+ {
+ if (_cleanupHandler != NULL) {
+ _cleanupHandler->HandleCleanup();
+ _cleanupHandler = NULL;
+ }
+ }
+};
+
diff --git a/fnet/src/vespa/fnet/frt/supervisor.cpp b/fnet/src/vespa/fnet/frt/supervisor.cpp
new file mode 100644
index 00000000000..7be527a9e64
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/supervisor.cpp
@@ -0,0 +1,516 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+
+FRT_Supervisor::FRT_Supervisor(FNET_Transport *transport,
+ FastOS_ThreadPool *threadPool)
+ : _transport(transport),
+ _threadPool(threadPool),
+ _standAlone(false),
+ _packetFactory(),
+ _packetStreamer(&_packetFactory),
+ _connector(NULL),
+ _reflectionManager(),
+ _rpcHooks(&_reflectionManager),
+ _connHooks(*this),
+ _methodMismatchHook(NULL)
+{
+ _rpcHooks.InitRPC(this);
+}
+
+
+FRT_Supervisor::FRT_Supervisor(uint32_t threadStackSize,
+ uint32_t maxThreads)
+ : _transport(NULL),
+ _threadPool(NULL),
+ _standAlone(true),
+ _packetFactory(),
+ _packetStreamer(&_packetFactory),
+ _connector(NULL),
+ _reflectionManager(),
+ _rpcHooks(&_reflectionManager),
+ _connHooks(*this),
+ _methodMismatchHook(NULL)
+{
+ _transport = new FNET_Transport();
+ assert(_transport != NULL);
+ if (threadStackSize > 0) {
+ _threadPool = new FastOS_ThreadPool(threadStackSize, maxThreads);
+ assert(_threadPool != NULL);
+ }
+ _rpcHooks.InitRPC(this);
+}
+
+
+FRT_Supervisor::~FRT_Supervisor()
+{
+ if (_standAlone) {
+ delete _transport;
+ delete _threadPool;
+ }
+ if (_connector != NULL) {
+ _connector->SubRef();
+ }
+ delete _methodMismatchHook;
+}
+
+
+bool
+FRT_Supervisor::Listen(const char *spec)
+{
+ if (_connector != NULL)
+ return false;
+ _connector = _transport->Listen(spec, &_packetStreamer, this);
+ return (_connector != NULL);
+}
+
+
+bool
+FRT_Supervisor::Listen(int port)
+{
+ char spec[32];
+ sprintf(spec, "tcp/%d", port);
+ return Listen(spec);
+}
+
+
+uint32_t
+FRT_Supervisor::GetListenPort() const
+{
+ return (_connector != NULL) ? _connector->GetPortNumber() : 0;
+}
+
+
+bool
+FRT_Supervisor::RunInvocation(FRT_RPCInvoker *invoker)
+{
+ // XXX: implement queue with max length + max # threads
+
+ if (_threadPool == NULL ||
+ _threadPool->NewThread(invoker) == NULL)
+ {
+ invoker->GetRequest()->SetError(FRTE_RPC_OVERLOAD,
+ "Could not start thread");
+ return false;
+ }
+ return true;
+}
+
+
+FRT_Target *
+FRT_Supervisor::GetTarget(const char *spec)
+{
+ FNET_TransportThread *thread = _transport->select_thread(spec, strlen(spec));
+ return new FRT_Target(thread->GetScheduler(),
+ thread->Connect(spec, &_packetStreamer));
+}
+
+
+FRT_Target *
+FRT_Supervisor::Get2WayTarget(const char *spec, FNET_Context connContext)
+{
+ FNET_TransportThread *thread = _transport->select_thread(spec, strlen(spec));
+ return new FRT_Target(thread->GetScheduler(),
+ thread->Connect(spec, &_packetStreamer,
+ NULL, FNET_Context(),
+ this, connContext));
+}
+
+
+FRT_Target *
+FRT_Supervisor::GetTarget(int port)
+{
+ char spec[64];
+ sprintf(spec, "tcp/localhost:%d", port);
+ return GetTarget(spec);
+}
+
+
+FRT_RPCRequest *
+FRT_Supervisor::AllocRPCRequest(FRT_RPCRequest *tradein)
+{
+ if (tradein != NULL) {
+ if (tradein->Recycle()) {
+ return tradein;
+ }
+ tradein->SubRef();
+ }
+ return new FRT_RPCRequest();
+}
+
+
+void
+FRT_Supervisor::SetSessionInitHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ _connHooks.SetSessionInitHook(method, handler);
+}
+
+
+void
+FRT_Supervisor::SetSessionDownHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ _connHooks.SetSessionDownHook(method, handler);
+}
+
+
+void
+FRT_Supervisor::SetSessionFiniHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ _connHooks.SetSessionFiniHook(method, handler);
+}
+
+
+void
+FRT_Supervisor::SetMethodMismatchHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ delete _methodMismatchHook;
+ _methodMismatchHook = new FRT_Method("frt.hook.methodMismatch", "*", "*",
+ true, method, handler);
+ assert(_methodMismatchHook != NULL);
+}
+
+
+void
+FRT_Supervisor::InvokeVoid(FNET_Connection *conn,
+ FRT_RPCRequest *req)
+{
+ if (conn != NULL) {
+ FNET_Channel *ch = conn->OpenChannel();
+ ch->Send(req->CreateRequestPacket(false));
+ ch->Free();
+ } else {
+ req->SubRef();
+ }
+}
+
+
+void
+FRT_Supervisor::InvokeAsync(SchedulerPtr scheduler,
+ FNET_Connection *conn,
+ FRT_RPCRequest *req,
+ double timeout,
+ FRT_IRequestWait *waiter)
+{
+ uint32_t chid;
+ FNET_Packet *packet = req->CreateRequestPacket(true);
+ FRT_RPCAdapter *adapter = new (req->GetMemoryTub()) FRT_RPCAdapter(scheduler.ptr, req, waiter);
+ FNET_Channel *ch = (conn == NULL)? NULL : conn->OpenChannel(adapter, FNET_Context((void *)req), &chid);
+
+ adapter->SetChannel(ch);
+ if (ch == NULL) {
+ packet->Free();
+ req->SetError(FRTE_RPC_CONNECTION);
+ adapter->ScheduleNow();
+ return;
+ }
+ if (timeout > 0.0) {
+ adapter->Schedule(timeout);
+ }
+ conn->PostPacket(packet, chid);
+}
+
+
+void
+FRT_Supervisor::InvokeSync(SchedulerPtr scheduler,
+ FNET_Connection *conn,
+ FRT_RPCRequest *req,
+ double timeout)
+{
+ FRT_SingleReqWait waiter;
+ InvokeAsync(scheduler, conn, req, timeout, &waiter);
+ waiter.WaitReq();
+}
+
+
+bool
+FRT_Supervisor::InitAdminChannel(FNET_Channel *channel)
+{
+ return _connHooks.InitAdminChannel(channel);
+}
+
+
+bool
+FRT_Supervisor::InitChannel(FNET_Channel *channel, uint32_t pcode)
+{
+ pcode &= 0xffff; // remove flags;
+ bool rc = false;
+ if (pcode >= PCODE_FRT_RPC_FIRST &&
+ pcode <= PCODE_FRT_RPC_LAST) {
+ FRT_RPCRequest *req = AllocRPCRequest();
+ channel->SetHandler(this);
+ channel->SetContext((void *)req);
+ if (req != NULL) {
+ req->SetContext(FNET_Context(channel));
+ rc = true;
+ }
+ }
+ return rc;
+}
+
+
+FNET_IPacketHandler::HP_RetCode
+FRT_Supervisor::HandlePacket(FNET_Packet *packet, FNET_Context context)
+{
+ uint32_t pcode = packet->GetPCODE() & 0xffff; // remove flags
+ FRT_RPCRequest *req = (FRT_RPCRequest *) context._value.VOIDP;
+ FRT_RPCInvoker *invoker = NULL;
+ bool noReply = false;
+
+ if (pcode == PCODE_FRT_RPC_REQUEST) {
+ noReply = ((FRT_RPCPacket *)packet)->NoReply();
+ } else {
+ req->SetError(FRTE_RPC_BAD_REQUEST);
+ }
+ invoker = new (req->GetMemoryTub()) FRT_RPCInvoker(this, req, noReply);
+ packet->Free();
+
+ if (req->IsError()) {
+
+ if (req->GetErrorCode() != FRTE_RPC_BAD_REQUEST
+ && _methodMismatchHook != NULL)
+ {
+ invoker->ForceMethod(_methodMismatchHook);
+ return (invoker->Invoke(false)) ?
+ FNET_FREE_CHANNEL : FNET_CLOSE_CHANNEL;
+ }
+
+ invoker->HandleDone(false);
+ return FNET_FREE_CHANNEL;
+
+ } else if (invoker->IsInstant()) {
+
+ return (invoker->Invoke(false)) ?
+ FNET_FREE_CHANNEL : FNET_CLOSE_CHANNEL;
+
+ } else {
+
+ if (!RunInvocation(invoker)) {
+ invoker->HandleDone(false);
+ return FNET_FREE_CHANNEL;
+ }
+ return FNET_CLOSE_CHANNEL;
+ }
+}
+
+
+bool
+FRT_Supervisor::Start()
+{
+ assert(_standAlone);
+ if (_threadPool == NULL)
+ return false;
+ return _transport->Start(_threadPool);
+}
+
+
+void
+FRT_Supervisor::Main()
+{
+ assert(_standAlone);
+ _transport->Main();
+}
+
+
+void
+FRT_Supervisor::ShutDown(bool waitFinished)
+{
+ assert(_standAlone);
+ _transport->ShutDown(waitFinished);
+}
+
+
+void
+FRT_Supervisor::WaitFinished()
+{
+ assert(_standAlone);
+ _transport->WaitFinished();
+}
+
+//----------------------------------------------------
+// RPC Hooks
+//----------------------------------------------------
+
+void
+FRT_Supervisor::RPCHooks::InitRPC(FRT_Supervisor *supervisor)
+{
+ FRT_ReflectionBuilder rb(supervisor);
+ //---------------------------------------------------------------------------
+ rb.DefineMethod("frt.rpc.ping", "", "", true,
+ FRT_METHOD(FRT_Supervisor::RPCHooks::RPC_Ping), this);
+ rb.MethodDesc("Method that may be used to check if the server is online");
+ //---------------------------------------------------------------------------
+ rb.DefineMethod("frt.rpc.echo", "*", "*", true,
+ FRT_METHOD(FRT_Supervisor::RPCHooks::RPC_Echo), this);
+ rb.MethodDesc("Echo the parameters as return values");
+ rb.ParamDesc("params", "Any set of parameters");
+ rb.ReturnDesc("return", "The parameter values");
+ //---------------------------------------------------------------------------
+ rb.DefineMethod("frt.rpc.getMethodList", "", "SSS", true,
+ FRT_METHOD(FRT_Supervisor::RPCHooks::RPC_GetMethodList),
+ this);
+ rb.MethodDesc("Obtain a list of all available methods");
+ rb.ReturnDesc("names", "Method names");
+ rb.ReturnDesc("params", "Method parameter types");
+ rb.ReturnDesc("return", "Method return types");
+ //---------------------------------------------------------------------------
+ rb.DefineMethod("frt.rpc.getMethodInfo", "s", "sssSSSS", true,
+ FRT_METHOD(FRT_Supervisor::RPCHooks::RPC_GetMethodInfo),
+ this);
+ rb.MethodDesc("Obtain detailed information about a single method");
+ rb.ParamDesc ("methodName", "The method we want information about");
+ rb.ReturnDesc("desc", "Description of what the method does");
+ rb.ReturnDesc("params", "Method parameter types");
+ rb.ReturnDesc("return", "Method return types");
+ rb.ReturnDesc("paramNames", "Method parameter names");
+ rb.ReturnDesc("paramDesc", "Method parameter descriptions");
+ rb.ReturnDesc("returnNames", "Method return value names");
+ rb.ReturnDesc("returnDesc", "Method return value descriptions");
+ //---------------------------------------------------------------------------
+}
+
+
+void
+FRT_Supervisor::RPCHooks::RPC_Ping(FRT_RPCRequest *req)
+{
+ (void) req;
+}
+
+
+void
+FRT_Supervisor::RPCHooks::RPC_Echo(FRT_RPCRequest *req)
+{
+ char tmp[1024];
+ FNET_DataBuffer buf(tmp, sizeof(tmp));
+ buf.EnsureFree(req->GetParams()->GetLength());
+ req->GetParams()->EncodeCopy(&buf);
+ req->GetReturn()->DecodeCopy(&buf, buf.GetDataLen());
+}
+
+
+void
+FRT_Supervisor::RPCHooks::RPC_GetMethodList(FRT_RPCRequest *req)
+{
+ _reflectionManager->DumpMethodList(req->GetReturn());
+}
+
+
+void
+FRT_Supervisor::RPCHooks::RPC_GetMethodInfo(FRT_RPCRequest *req)
+{
+ FRT_Values &arg = *req->GetParams();
+
+ FRT_Method *info = _reflectionManager->LookupMethod(arg[0]._string._str);
+ if (info != NULL) {
+ info->GetDocumentation(req->GetReturn());
+ } else {
+ req->SetError(FRTE_RPC_METHOD_FAILED, "No such method");
+ }
+}
+
+//----------------------------------------------------
+// Connection Hooks
+//----------------------------------------------------
+
+FRT_Supervisor::ConnHooks::ConnHooks(FRT_Supervisor &parent)
+ : _parent(parent),
+ _sessionInitHook(NULL),
+ _sessionDownHook(NULL),
+ _sessionFiniHook(NULL)
+{
+}
+
+
+FRT_Supervisor::ConnHooks::~ConnHooks()
+{
+ delete _sessionInitHook;
+ delete _sessionDownHook;
+ delete _sessionFiniHook;
+}
+
+
+void
+FRT_Supervisor::ConnHooks::SetSessionInitHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ delete _sessionInitHook;
+ _sessionInitHook = new FRT_Method("frt.hook.sessionInit", "", "",
+ true, method, handler);
+ assert(_sessionInitHook != NULL);
+}
+
+
+void
+FRT_Supervisor::ConnHooks::SetSessionDownHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ delete _sessionDownHook;
+ _sessionDownHook = new FRT_Method("frt.hook.sessionDown", "", "",
+ true, method, handler);
+ assert(_sessionDownHook != NULL);
+}
+
+
+void
+FRT_Supervisor::ConnHooks::SetSessionFiniHook(FRT_METHOD_PT method,
+ FRT_Invokable *handler)
+{
+ delete _sessionFiniHook;
+ _sessionFiniHook = new FRT_Method("frt.hook.sessionFini", "", "",
+ true, method, handler);
+ assert(_sessionFiniHook != NULL);
+}
+
+
+void
+FRT_Supervisor::ConnHooks::InvokeHook(FRT_Method *hook,
+ FNET_Connection *conn)
+{
+ FRT_RPCRequest *req = _parent.AllocRPCRequest();
+ req->SetMethodName(hook->GetName());
+ (new (req->GetMemoryTub()) FRT_HookInvoker(req, hook, conn))->Invoke();
+}
+
+
+bool
+FRT_Supervisor::ConnHooks::InitAdminChannel(FNET_Channel *channel)
+{
+ FNET_Connection *conn = channel->GetConnection();
+ conn->SetCleanupHandler(this);
+ if (_sessionInitHook != NULL) {
+ InvokeHook(_sessionInitHook, conn);
+ }
+ channel->SetHandler(this);
+ channel->SetContext(channel);
+ return true;
+}
+
+
+FNET_IPacketHandler::HP_RetCode
+FRT_Supervisor::ConnHooks::HandlePacket(FNET_Packet *packet,
+ FNET_Context context)
+{
+ if (!packet->IsChannelLostCMD()) {
+ packet->Free();
+ return FNET_KEEP_CHANNEL;
+ }
+ FNET_Channel *ch = context._value.CHANNEL;
+ if (_sessionDownHook != NULL) {
+ InvokeHook(_sessionDownHook, ch->GetConnection());
+ }
+ return FNET_FREE_CHANNEL;
+}
+
+
+void
+FRT_Supervisor::ConnHooks::Cleanup(FNET_Connection *conn)
+{
+ if (_sessionFiniHook != NULL) {
+ InvokeHook(_sessionFiniHook, conn);
+ }
+}
diff --git a/fnet/src/vespa/fnet/frt/supervisor.h b/fnet/src/vespa/fnet/frt/supervisor.h
new file mode 100644
index 00000000000..dbf7bd20bce
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/supervisor.h
@@ -0,0 +1,137 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+
+class FRT_Supervisor : public FNET_IServerAdapter,
+ public FNET_IPacketHandler
+{
+public:
+ class RPCHooks : public FRT_Invokable
+ {
+ private:
+ FRT_ReflectionManager *_reflectionManager;
+
+ RPCHooks(const RPCHooks &);
+ RPCHooks &operator=(const RPCHooks &);
+
+ public:
+ RPCHooks(FRT_ReflectionManager *reflect)
+ : _reflectionManager(reflect) {}
+
+ void InitRPC(FRT_Supervisor *supervisor);
+ void RPC_Ping(FRT_RPCRequest *req);
+ void RPC_Echo(FRT_RPCRequest *req);
+ void RPC_GetMethodList(FRT_RPCRequest *req);
+ void RPC_GetMethodInfo(FRT_RPCRequest *req);
+ };
+
+ class ConnHooks : public FNET_IConnectionCleanupHandler,
+ public FNET_IPacketHandler
+ {
+ private:
+ FRT_Supervisor &_parent;
+ FRT_Method *_sessionInitHook;
+ FRT_Method *_sessionDownHook;
+ FRT_Method *_sessionFiniHook;
+
+ ConnHooks(const ConnHooks &);
+ ConnHooks &operator=(const ConnHooks &);
+
+ public:
+ ConnHooks(FRT_Supervisor &parent);
+ virtual ~ConnHooks();
+
+ void SetSessionInitHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void SetSessionDownHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void SetSessionFiniHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void InvokeHook(FRT_Method *hook, FNET_Connection *conn);
+ bool InitAdminChannel(FNET_Channel *channel);
+ HP_RetCode HandlePacket(FNET_Packet *packet, FNET_Context context);
+ void Cleanup(FNET_Connection *conn);
+ };
+
+private:
+ FNET_Transport *_transport;
+ FastOS_ThreadPool *_threadPool;
+ bool _standAlone;
+
+ FRT_PacketFactory _packetFactory;
+ FNET_SimplePacketStreamer _packetStreamer;
+ FNET_Connector *_connector;
+ FRT_ReflectionManager _reflectionManager;
+ RPCHooks _rpcHooks;
+ ConnHooks _connHooks;
+ FRT_Method *_methodMismatchHook;
+
+ FRT_Supervisor(const FRT_Supervisor &);
+ FRT_Supervisor &operator=(const FRT_Supervisor &);
+
+public:
+ FRT_Supervisor(FNET_Transport *transport,
+ FastOS_ThreadPool *threadPool);
+ FRT_Supervisor(uint32_t threadStackSize = 65000,
+ uint32_t maxThreads = 0);
+ virtual ~FRT_Supervisor();
+
+ bool StandAlone() { return _standAlone; }
+ FNET_Transport *GetTransport() { return _transport; }
+ FNET_Scheduler *GetScheduler() { return _transport->GetScheduler(); }
+ FastOS_ThreadPool *GetThreadPool() { return _threadPool; }
+ FRT_ReflectionManager *GetReflectionManager() { return &_reflectionManager; }
+
+ bool Listen(const char *spec);
+ bool Listen(int port);
+ uint32_t GetListenPort() const;
+
+ bool RunInvocation(FRT_RPCInvoker *invoker);
+
+ FRT_Target *GetTarget(const char *spec);
+ FRT_Target *Get2WayTarget(const char *spec,
+ FNET_Context connContext = FNET_Context());
+ FRT_Target *GetTarget(int port);
+ FRT_RPCRequest *AllocRPCRequest(FRT_RPCRequest *tradein = NULL);
+
+ // special hooks (implemented as RPC methods)
+ void SetSessionInitHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void SetSessionDownHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void SetSessionFiniHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+ void SetMethodMismatchHook(FRT_METHOD_PT method, FRT_Invokable *handler);
+
+ struct SchedulerPtr {
+ FNET_Scheduler *ptr;
+ SchedulerPtr(FNET_Scheduler *scheduler)
+ : ptr(scheduler) {}
+ SchedulerPtr(FNET_Transport *transport)
+ : ptr(transport->GetScheduler()) {}
+ SchedulerPtr(FNET_TransportThread *transport_thread)
+ : ptr(transport_thread->GetScheduler()) {}
+ };
+
+ // methods for performing rpc invocations
+ static void InvokeVoid(FNET_Connection *conn,
+ FRT_RPCRequest *req);
+ static void InvokeAsync(SchedulerPtr scheduler,
+ FNET_Connection *conn,
+ FRT_RPCRequest *req,
+ double timeout,
+ FRT_IRequestWait *waiter);
+ static void InvokeSync(SchedulerPtr scheduler,
+ FNET_Connection *conn,
+ FRT_RPCRequest *req,
+ double timeout);
+
+ // FNET ServerAdapter Interface
+ bool InitAdminChannel(FNET_Channel *channel);
+ bool InitChannel(FNET_Channel *channel, uint32_t pcode);
+
+ // Packet Handling
+ HP_RetCode HandlePacket(FNET_Packet *packet, FNET_Context context);
+
+ // Methods for controlling transport object in standalone mode
+ bool Start();
+ void Main();
+ void ShutDown(bool waitFinished);
+ void WaitFinished();
+};
+
diff --git a/fnet/src/vespa/fnet/frt/target.cpp b/fnet/src/vespa/fnet/frt/target.cpp
new file mode 100644
index 00000000000..ee4d3e672fe
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/target.cpp
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+FRT_Target::~FRT_Target()
+{
+ assert(_refcnt == 0);
+ FNET_Connection * conn(_conn);
+ _conn = NULL;
+ if (conn != NULL) {
+ conn->Owner()->Close(conn, /* needref */ false);
+ }
+}
+
diff --git a/fnet/src/vespa/fnet/frt/target.h b/fnet/src/vespa/fnet/frt/target.h
new file mode 100644
index 00000000000..62586da0e65
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/target.h
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/util/atomic.h>
+
+class FRT_Target
+{
+private:
+ int _refcnt;
+ FNET_Scheduler *_scheduler;
+ FNET_Connection *_conn;
+
+ FRT_Target(const FRT_Target &);
+ FRT_Target &operator=(const FRT_Target &);
+
+public:
+ FRT_Target(FNET_Scheduler *scheduler, FNET_Connection *conn)
+ : _refcnt(1),
+ _scheduler(scheduler),
+ _conn(conn) {}
+
+ ~FRT_Target();
+
+ FNET_Connection *GetConnection() const { return _conn; }
+
+ void AddRef() { vespalib::Atomic::postInc(&_refcnt); }
+ void SubRef() {
+ if (vespalib::Atomic::postDec(&_refcnt) == 1) {
+ delete this;
+ }
+ }
+
+ int GetRefCnt() const { return _refcnt; }
+
+ bool IsValid()
+ {
+ return ((_conn != NULL) &&
+ (_conn->GetState() <= FNET_Connection::FNET_CONNECTED));
+ }
+
+ void InvokeAsync(FRT_RPCRequest *req, double timeout, FRT_IRequestWait *waiter)
+ {
+ FRT_Supervisor::InvokeAsync(_scheduler, _conn, req, timeout, waiter);
+ }
+
+ void InvokeVoid(FRT_RPCRequest *req)
+ {
+ FRT_Supervisor::InvokeVoid(_conn, req);
+ }
+
+ void InvokeSync(FRT_RPCRequest *req, double timeout)
+ {
+ FRT_Supervisor::InvokeSync(_scheduler, _conn, req, timeout);
+ }
+};
diff --git a/fnet/src/vespa/fnet/frt/values.cpp b/fnet/src/vespa/fnet/frt/values.cpp
new file mode 100644
index 00000000000..007b3c848e6
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/values.cpp
@@ -0,0 +1,1473 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/frt/frt.h>
+
+
+void
+FRT_Values::Print(uint32_t indent)
+{
+ printf("%*sFRT_Values {\n", indent, "");
+ printf("%*s [%s]\n", indent, "",
+ (_numValues > 0)? _typeString : "(Empty)");
+
+ const char *p = _typeString;
+ for (uint32_t i = 0; i < _numValues; i++, p++) {
+ Print(_values[i], *p, indent + 2);
+ }
+ printf("%*s}\n", indent, "");
+}
+
+
+uint32_t
+FRT_Values::GetLength()
+{
+ uint32_t numValues = _numValues;
+ const char *p = _typeString;
+ uint32_t len = sizeof(uint32_t) + numValues;
+ for (uint32_t i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ len += sizeof(uint8_t);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._int8_array._len * sizeof(uint8_t));
+ break;
+
+ case FRT_VALUE_INT16:
+ len += sizeof(uint16_t);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._int16_array._len * sizeof(uint16_t));
+ break;
+
+ case FRT_VALUE_INT32:
+ len += sizeof(uint32_t);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._int32_array._len * sizeof(uint32_t));
+ break;
+
+ case FRT_VALUE_INT64:
+ len += sizeof(uint64_t);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._int64_array._len * sizeof(uint64_t));
+ break;
+
+ case FRT_VALUE_FLOAT:
+ len += sizeof(float);
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._float_array._len * sizeof(float));
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ len += sizeof(double);
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ len += (sizeof(uint32_t)
+ + _values[i]._double_array._len * sizeof(double));
+ break;
+
+ case FRT_VALUE_STRING:
+ len += sizeof(uint32_t) + _values[i]._string._len;
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ len += (sizeof(uint32_t)
+ + _values[i]._string_array._len * sizeof(uint32_t));
+
+ uint32_t num = _values[i]._string_array._len;
+ FRT_StringValue *pt = _values[i]._string_array._pt;
+
+ for (; num > 0; num--, pt++)
+ len += pt->_len;
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ len += sizeof(uint32_t) + _values[i]._data._len;
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ len += (sizeof(uint32_t)
+ + _values[i]._data_array._len * sizeof(uint32_t));
+
+ uint32_t num = _values[i]._data_array._len;
+ FRT_DataValue *pt = _values[i]._data_array._pt;
+
+ for (; num > 0; num--, pt++)
+ len += pt->_len;
+ }
+ break;
+
+ default:
+ assert(false);
+ }
+ }
+ return len;
+}
+
+
+bool
+FRT_Values::DecodeCopy(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t numValues;
+ const char *typeString;
+ const char *p;
+ uint32_t i;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&numValues, sizeof(numValues));
+ len -= sizeof(uint32_t);
+ EnsureFree(numValues);
+
+ if (len < numValues) goto error;
+ typeString = src->GetData();
+ src->DataToDead(numValues);
+ len -= numValues;
+
+ p = typeString;
+ for (i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ if (len < sizeof(uint8_t)) goto error;
+ AddInt8(src->ReadInt8());
+ len -= sizeof(uint8_t);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t arrlen;
+ uint8_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint8_t)) goto error;
+ len -= arrlen * sizeof(uint8_t);
+ arr = AddInt8Array(arrlen);
+ src->ReadBytes(arr, arrlen);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ {
+ uint16_t tmp;
+
+ if (len < sizeof(uint16_t)) goto error;
+ src->ReadBytes(&tmp, sizeof(tmp));
+ AddInt16(tmp);
+ len -= sizeof(uint16_t);
+ }
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t arrlen;
+ uint16_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint16_t)) goto error;
+ len -= arrlen * sizeof(uint16_t);
+ arr = AddInt16Array(arrlen);
+ src->ReadBytes(arr, arrlen * sizeof(uint16_t));
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ {
+ uint32_t tmp;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&tmp, sizeof(tmp));
+ AddInt32(tmp);
+ len -= sizeof(uint32_t);
+ }
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint32_t)) goto error;
+ len -= arrlen * sizeof(uint32_t);
+ arr = AddInt32Array(arrlen);
+ src->ReadBytes(arr, arrlen * sizeof(uint32_t));
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ {
+ uint64_t tmp;
+
+ if (len < sizeof(uint64_t)) goto error;
+ src->ReadBytes(&tmp, sizeof(tmp));
+ AddInt64(tmp);
+ len -= sizeof(uint64_t);
+ }
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint64_t)) goto error;
+ len -= arrlen * sizeof(uint64_t);
+ arr = AddInt64Array(arrlen);
+ src->ReadBytes(arr, arrlen * sizeof(uint64_t));
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ {
+ union { uint32_t INT32; float FLOAT; } val;
+ if (len < sizeof(float)) goto error;
+ src->ReadBytes(&(val.INT32), sizeof(uint32_t));
+ AddFloat(val.FLOAT);
+ len -= sizeof(float);
+ }
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(float)) goto error;
+ len -= arrlen * sizeof(float);
+ arr = (uint32_t *) AddFloatArray(arrlen);
+ src->ReadBytes(arr, arrlen * sizeof(uint32_t));
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ {
+ union { uint64_t INT64; double DOUBLE; } val;
+ if (len < sizeof(double)) goto error;
+ src->ReadBytes(&(val.INT64), sizeof(uint64_t));
+ AddDouble(val.DOUBLE);
+ len -= sizeof(double);
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(double)) goto error;
+ len -= arrlen * sizeof(double);
+ arr = (uint64_t *) AddDoubleArray(arrlen);
+ src->ReadBytes(arr, arrlen * sizeof(uint64_t));
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t slen;
+ src->ReadBytes(&slen, sizeof(slen));
+ len -= sizeof(uint32_t);
+ if (len < slen) goto error;
+ AddString(src->GetData(), slen);
+ src->DataToDead(slen);
+ len -= slen;
+ }
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_StringValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ arr = AddStringArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&(arr->_len), sizeof(uint32_t));
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetString(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t dlen;
+ src->ReadBytes(&dlen, sizeof(dlen));
+ len -= sizeof(uint32_t);
+ if (len < dlen) goto error;
+ AddData(src->GetData(), dlen);
+ src->DataToDead(dlen);
+ len -= dlen;
+ }
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_DataValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&arrlen, sizeof(arrlen));
+ len -= sizeof(uint32_t);
+ arr = AddDataArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ src->ReadBytes(&(arr->_len), sizeof(uint32_t));
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetData(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ default:
+ goto error;
+ }
+ }
+
+ if (len != 0) goto error;
+ if (strncmp(typeString, _typeString, numValues) != 0) goto error;
+ return true;
+
+error:
+ src->DataToDead(len);
+ return false;
+}
+
+
+bool
+FRT_Values::DecodeBig(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t numValues;
+ const char *typeString;
+ const char *p;
+ uint32_t i;
+
+ if (len < sizeof(uint32_t)) goto error;
+ numValues = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ EnsureFree(numValues);
+
+ if (len < numValues) goto error;
+ typeString = src->GetData();
+ src->DataToDead(numValues);
+ len -= numValues;
+
+ p = typeString;
+ for (i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ if (len < sizeof(uint8_t)) goto error;
+ AddInt8(src->ReadInt8());
+ len -= sizeof(uint8_t);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t arrlen;
+ uint8_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint8_t)) goto error;
+ len -= arrlen * sizeof(uint8_t);
+ arr = AddInt8Array(arrlen);
+ src->ReadBytes(arr, arrlen);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ if (len < sizeof(uint16_t)) goto error;
+ AddInt16(src->ReadInt16());
+ len -= sizeof(uint16_t);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t arrlen;
+ uint16_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint16_t)) goto error;
+ len -= arrlen * sizeof(uint16_t);
+ arr = AddInt16Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt16();
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ if (len < sizeof(uint32_t)) goto error;
+ AddInt32(src->ReadInt32());
+ len -= sizeof(uint32_t);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint32_t)) goto error;
+ len -= arrlen * sizeof(uint32_t);
+ arr = AddInt32Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt32();
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ if (len < sizeof(uint64_t)) goto error;
+ AddInt64(src->ReadInt64());
+ len -= sizeof(uint64_t);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint64_t)) goto error;
+ len -= arrlen * sizeof(uint64_t);
+ arr = AddInt64Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt64();
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ {
+ union { uint32_t INT32; float FLOAT; } val;
+ if (len < sizeof(float)) goto error;
+ val.INT32 = src->ReadInt32();
+ AddFloat(val.FLOAT);
+ len -= sizeof(float);
+ }
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(float)) goto error;
+ len -= arrlen * sizeof(float);
+ arr = (uint32_t *) AddFloatArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt32();
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ {
+ union { uint64_t INT64; double DOUBLE; } val;
+ if (len < sizeof(double)) goto error;
+ val.INT64 = src->ReadInt64();
+ AddDouble(val.DOUBLE);
+ len -= sizeof(double);
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(double)) goto error;
+ len -= arrlen * sizeof(double);
+ arr = (uint64_t *) AddDoubleArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt64();
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t slen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < slen) goto error;
+ AddString(src->GetData(), slen);
+ src->DataToDead(slen);
+ len -= slen;
+ }
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_StringValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ arr = AddStringArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ arr->_len = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetString(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t dlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < dlen) goto error;
+ AddData(src->GetData(), dlen);
+ src->DataToDead(dlen);
+ len -= dlen;
+ }
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_DataValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ arr = AddDataArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ arr->_len = src->ReadInt32();
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetData(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ default:
+ goto error;
+ }
+ }
+
+ if (len != 0) goto error;
+ if (strncmp(typeString, _typeString, numValues) != 0) goto error;
+ return true;
+
+error:
+ src->DataToDead(len);
+ return false;
+}
+
+
+bool
+FRT_Values::DecodeLittle(FNET_DataBuffer *src, uint32_t len)
+{
+ uint32_t numValues;
+ const char *typeString;
+ const char *p;
+ uint32_t i;
+
+ if (len < sizeof(uint32_t)) goto error;
+ numValues = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ EnsureFree(numValues);
+
+ if (len < numValues) goto error;
+ typeString = src->GetData();
+ src->DataToDead(numValues);
+ len -= numValues;
+
+ p = typeString;
+ for (i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ if (len < sizeof(uint8_t)) goto error;
+ AddInt8(src->ReadInt8());
+ len -= sizeof(uint8_t);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t arrlen;
+ uint8_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint8_t)) goto error;
+ len -= arrlen * sizeof(uint8_t);
+ arr = AddInt8Array(arrlen);
+ src->ReadBytes(arr, arrlen);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ if (len < sizeof(uint16_t)) goto error;
+ AddInt16(src->ReadInt16Reverse());
+ len -= sizeof(uint16_t);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t arrlen;
+ uint16_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint16_t)) goto error;
+ len -= arrlen * sizeof(uint16_t);
+ arr = AddInt16Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt16Reverse();
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ if (len < sizeof(uint32_t)) goto error;
+ AddInt32(src->ReadInt32Reverse());
+ len -= sizeof(uint32_t);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint32_t)) goto error;
+ len -= arrlen * sizeof(uint32_t);
+ arr = AddInt32Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt32Reverse();
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ if (len < sizeof(uint64_t)) goto error;
+ AddInt64(src->ReadInt64Reverse());
+ len -= sizeof(uint64_t);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(uint64_t)) goto error;
+ len -= arrlen * sizeof(uint64_t);
+ arr = AddInt64Array(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt64Reverse();
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ {
+ union { uint32_t INT32; float FLOAT; } val;
+ if (len < sizeof(float)) goto error;
+ val.INT32 = src->ReadInt32Reverse();
+ AddFloat(val.FLOAT);
+ len -= sizeof(float);
+ }
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t arrlen;
+ uint32_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(float)) goto error;
+ len -= arrlen * sizeof(float);
+ arr = (uint32_t *) AddFloatArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt32Reverse();
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ {
+ union { uint64_t INT64; double DOUBLE; } val;
+ if (len < sizeof(double)) goto error;
+ val.INT64 = src->ReadInt64Reverse();
+ AddDouble(val.DOUBLE);
+ len -= sizeof(double);
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t arrlen;
+ uint64_t *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arrlen * sizeof(double)) goto error;
+ len -= arrlen * sizeof(double);
+ arr = (uint64_t *) AddDoubleArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++)
+ *arr = src->ReadInt64Reverse();
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t slen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < slen) goto error;
+ AddString(src->GetData(), slen);
+ src->DataToDead(slen);
+ len -= slen;
+ }
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_StringValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ arr = AddStringArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ arr->_len = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetString(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ {
+ if (len < sizeof(uint32_t)) goto error;
+ uint32_t dlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < dlen) goto error;
+ AddData(src->GetData(), dlen);
+ src->DataToDead(dlen);
+ len -= dlen;
+ }
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t arrlen;
+ FRT_DataValue *arr;
+
+ if (len < sizeof(uint32_t)) goto error;
+ arrlen = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ arr = AddDataArray(arrlen);
+ for (; arrlen > 0; arrlen--, arr++) {
+ if (len < sizeof(uint32_t)) goto error;
+ arr->_len = src->ReadInt32Reverse();
+ len -= sizeof(uint32_t);
+ if (len < arr->_len) goto error;
+ SetData(arr, src->GetData(), arr->_len);
+ src->DataToDead(arr->_len);
+ len -= arr->_len;
+ }
+ }
+ break;
+
+ default:
+ goto error;
+ }
+ }
+
+ if (len != 0) goto error;
+ if (strncmp(typeString, _typeString, numValues) != 0) goto error;
+ return true;
+
+error:
+ src->DataToDead(len);
+ return false;
+}
+
+
+void
+FRT_Values::EncodeCopy(FNET_DataBuffer *dst)
+{
+ uint32_t numValues = _numValues;
+ const char *p = _typeString;
+
+ dst->WriteBytesFast(&numValues, sizeof(numValues));
+ dst->WriteBytesFast(p, numValues);
+
+ for (uint32_t i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ dst->WriteInt8Fast(_values[i]._intval8);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t len = _values[i]._int8_array._len;
+ uint8_t *pt = _values[i]._int8_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ dst->WriteBytesFast(&(_values[i]._intval16), sizeof(uint16_t));
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t len = _values[i]._int16_array._len;
+ uint16_t *pt = _values[i]._int16_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len * sizeof(uint16_t));
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ dst->WriteBytesFast(&(_values[i]._intval32), sizeof(uint32_t));
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t len = _values[i]._int32_array._len;
+ uint32_t *pt = _values[i]._int32_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len * sizeof(uint32_t));
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ dst->WriteBytesFast(&(_values[i]._intval64), sizeof(uint64_t));
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t len = _values[i]._int64_array._len;
+ uint64_t *pt = _values[i]._int64_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len * sizeof(uint64_t));
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ dst->WriteBytesFast(&(_values[i]._intval32), sizeof(uint32_t));
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t len = _values[i]._float_array._len;
+ uint32_t *pt = (uint32_t *) _values[i]._float_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len * sizeof(uint32_t));
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ dst->WriteBytesFast(&(_values[i]._intval64), sizeof(uint64_t));
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t len = _values[i]._double_array._len;
+ uint64_t *pt = (uint64_t *) _values[i]._double_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ dst->WriteBytesFast(pt, len * sizeof(uint64_t));
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ dst->WriteBytesFast(&(_values[i]._string._len), sizeof(uint32_t));
+ dst->WriteBytesFast(_values[i]._string._str,
+ _values[i]._string._len);
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t len = _values[i]._string_array._len;
+ FRT_StringValue *pt = _values[i]._string_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ for (; len > 0; len--, pt++) {
+ dst->WriteBytesFast(&(pt->_len), sizeof(uint32_t));
+ dst->WriteBytesFast(pt->_str, pt->_len);
+ }
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ dst->WriteBytesFast(&(_values[i]._data._len), sizeof(uint32_t));
+ dst->WriteBytesFast(_values[i]._data._buf,
+ _values[i]._data._len);
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t len = _values[i]._data_array._len;
+ FRT_DataValue *pt = _values[i]._data_array._pt;
+
+ dst->WriteBytesFast(&len, sizeof(len));
+ for (; len > 0; len--, pt++) {
+ dst->WriteBytesFast(&(pt->_len), sizeof(uint32_t));
+ dst->WriteBytesFast(pt->_buf, pt->_len);
+ }
+ }
+ break;
+
+ default:
+ assert(false);
+ }
+ }
+}
+
+
+void
+FRT_Values::EncodeBig(FNET_DataBuffer *dst)
+{
+ uint32_t numValues = _numValues;
+ const char *p = _typeString;
+
+ dst->WriteInt32Fast(numValues);
+ dst->WriteBytesFast(p, numValues);
+
+ for (uint32_t i = 0; i < numValues; i++, p++) {
+
+ switch (*p) {
+
+ case FRT_VALUE_INT8:
+ dst->WriteInt8Fast(_values[i]._intval8);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t len = _values[i]._int8_array._len;
+ uint8_t *pt = _values[i]._int8_array._pt;
+
+ dst->WriteInt32Fast(len);
+ dst->WriteBytesFast(pt, len);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ dst->WriteInt16Fast(_values[i]._intval16);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t len = _values[i]._int16_array._len;
+ uint16_t *pt = _values[i]._int16_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++)
+ dst->WriteInt16Fast(*pt);
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ dst->WriteInt32Fast(_values[i]._intval32);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t len = _values[i]._int32_array._len;
+ uint32_t *pt = _values[i]._int32_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++)
+ dst->WriteInt32Fast(*pt);
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ dst->WriteInt64Fast(_values[i]._intval64);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t len = _values[i]._int64_array._len;
+ uint64_t *pt = _values[i]._int64_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++)
+ dst->WriteInt64Fast(*pt);
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ dst->WriteInt32Fast(_values[i]._intval32);
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t len = _values[i]._float_array._len;
+ uint32_t *pt = (uint32_t *) _values[i]._float_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++)
+ dst->WriteInt32Fast(*pt);
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ dst->WriteInt64Fast(_values[i]._intval64);
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t len = _values[i]._double_array._len;
+ uint64_t *pt = (uint64_t *) _values[i]._double_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++)
+ dst->WriteInt64Fast(*pt);
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ dst->WriteInt32Fast(_values[i]._string._len);
+ dst->WriteBytesFast(_values[i]._string._str,
+ _values[i]._string._len);
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t len = _values[i]._string_array._len;
+ FRT_StringValue *pt = _values[i]._string_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++) {
+ dst->WriteInt32Fast(pt->_len);
+ dst->WriteBytesFast(pt->_str, pt->_len);
+ }
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ dst->WriteInt32Fast(_values[i]._data._len);
+ dst->WriteBytesFast(_values[i]._data._buf,
+ _values[i]._data._len);
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t len = _values[i]._data_array._len;
+ FRT_DataValue *pt = _values[i]._data_array._pt;
+
+ dst->WriteInt32Fast(len);
+ for (; len > 0; len--, pt++) {
+ dst->WriteInt32Fast(pt->_len);
+ dst->WriteBytesFast(pt->_buf, pt->_len);
+ }
+ }
+ break;
+
+ default:
+ assert(false);
+ }
+ }
+}
+
+
+bool
+FRT_Values::Equals(FRT_Values *values)
+{
+ if (values->GetNumValues() != GetNumValues())
+ return false;
+
+ if (GetNumValues() == 0)
+ return true;
+
+ if (strcmp(values->GetTypeString(), GetTypeString()) != 0)
+ return false;
+
+ for(uint32_t i = 0; i < GetNumValues(); i++)
+ if (!Equals(values->GetValue(i), GetValue(i), GetType(i)))
+ return false;
+
+ return true;
+}
+
+
+void
+FRT_Values::Print(FRT_Value value, uint32_t type,
+ uint32_t indent)
+{
+ switch (type) {
+
+ case FRT_VALUE_INT8:
+ printf("%*sint8: %u\n", indent, "", value._intval8);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t len = value._int8_array._len;
+ uint8_t *pt = value._int8_array._pt;
+
+ printf("%*sint8_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s int8: %u\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ printf("%*sint16: %u\n", indent, "", value._intval16);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t len = value._int16_array._len;
+ uint16_t *pt = value._int16_array._pt;
+
+ printf("%*sint16_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s int16: %u\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ printf("%*sint32: %u\n", indent, "", value._intval32);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t len = value._int32_array._len;
+ uint32_t *pt = value._int32_array._pt;
+
+ printf("%*sint32_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s int32: %u\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ printf("%*sint64: %" PRIu64 "\n", indent, "", value._intval64);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t len = value._int64_array._len;
+ uint64_t *pt = value._int64_array._pt;
+
+ printf("%*sint64_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s int64: %" PRIu64 "\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ printf("%*sfloat: %f\n", indent, "", value._float);
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t len = value._float_array._len;
+ float *pt = value._float_array._pt;
+
+ printf("%*sfloat_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s float: %f\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ printf("%*sdouble: %f\n", indent, "", value._double);
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t len = value._double_array._len;
+ double *pt = value._double_array._pt;
+
+ printf("%*sdouble_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s double: %f\n", indent, "", *pt);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ printf("%*sstring: %s\n", indent, "", value._string._str);
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t len = value._string_array._len;
+ FRT_StringValue *pt = value._string_array._pt;
+
+ printf("%*sstring_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s string: %s\n", indent, "", pt->_str);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ printf("%*sdata: len=%u\n", indent, "", value._data._len);
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t len = value._data_array._len;
+ FRT_DataValue *pt = value._data_array._pt;
+
+ printf("%*sdata_array {\n", indent, "");
+ for (; len > 0; len--, pt++)
+ printf("%*s data: len=%u\n", indent, "", pt->_len);
+ printf("%*s}\n", indent, "");
+ }
+ break;
+
+ default:
+ assert(false);
+ }
+}
+
+
+bool
+FRT_Values::Equals(FRT_Value a, FRT_Value b,
+ uint32_t type)
+{
+ bool rc = true;
+
+ switch (type) {
+
+ case FRT_VALUE_INT8:
+ rc = (a._intval8 == b._intval8);
+ break;
+
+ case FRT_VALUE_INT8_ARRAY:
+ {
+ uint32_t len = a._int8_array._len;
+ uint8_t *pt_a = a._int8_array._pt;
+ uint8_t *pt_b = b._int8_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_INT16:
+ rc = (a._intval16 == b._intval16);
+ break;
+
+ case FRT_VALUE_INT16_ARRAY:
+ {
+ uint32_t len = a._int16_array._len;
+ uint16_t *pt_a = a._int16_array._pt;
+ uint16_t *pt_b = b._int16_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_INT32:
+ rc = (a._intval32 == b._intval32);
+ break;
+
+ case FRT_VALUE_INT32_ARRAY:
+ {
+ uint32_t len = a._int32_array._len;
+ uint32_t *pt_a = a._int32_array._pt;
+ uint32_t *pt_b = b._int32_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_INT64:
+ rc = (a._intval64 == b._intval64);
+ break;
+
+ case FRT_VALUE_INT64_ARRAY:
+ {
+ uint32_t len = a._int64_array._len;
+ uint64_t *pt_a = a._int64_array._pt;
+ uint64_t *pt_b = b._int64_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_FLOAT:
+ rc = (a._float == b._float);
+ break;
+
+ case FRT_VALUE_FLOAT_ARRAY:
+ {
+ uint32_t len = a._float_array._len;
+ float *pt_a = a._float_array._pt;
+ float *pt_b = b._float_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_DOUBLE:
+ rc = (a._double == b._double);
+ break;
+
+ case FRT_VALUE_DOUBLE_ARRAY:
+ {
+ uint32_t len = a._double_array._len;
+ double *pt_a = a._double_array._pt;
+ double *pt_b = b._double_array._pt;
+
+ for (; rc && len > 0; len--)
+ rc = (*pt_a++ == *pt_b++);
+ }
+ break;
+
+ case FRT_VALUE_STRING:
+ rc = (a._string._len == b._string._len &&
+ memcmp(a._string._str, b._string._str, a._string._len) == 0);
+ break;
+
+ case FRT_VALUE_STRING_ARRAY:
+ {
+ uint32_t len = a._string_array._len;
+ FRT_StringValue *pt_a = a._string_array._pt;
+ FRT_StringValue *pt_b = b._string_array._pt;
+
+ for (; rc && len > 0; len--, pt_a++, pt_b++)
+ rc = (pt_a->_len == pt_b->_len &&
+ memcmp(pt_a->_str, pt_b->_str, pt_a->_len) == 0);
+ }
+ break;
+
+ case FRT_VALUE_DATA:
+ rc = (a._data._len == b._data._len &&
+ memcmp(a._data._buf, b._data._buf, a._data._len) == 0);
+ break;
+
+ case FRT_VALUE_DATA_ARRAY:
+ {
+ uint32_t len = a._data_array._len;
+ FRT_DataValue *pt_a = a._data_array._pt;
+ FRT_DataValue *pt_b = b._data_array._pt;
+
+ for (; rc && len > 0; len--, pt_a++, pt_b++)
+ rc = (pt_a->_len == pt_b->_len &&
+ memcmp(pt_a->_buf, pt_b->_buf, pt_a->_len) == 0);
+ }
+ break;
+
+ default:
+ rc = false;
+ }
+ return rc;
+}
+
+
+bool
+FRT_Values::Equals(FRT_Value a, uint32_t a_type,
+ FRT_Value b, uint32_t b_type)
+{
+ return (a_type == b_type) ?
+ Equals(a, b, a_type) : false;
+}
+
+
+bool
+FRT_Values::CheckTypes(const char *spec, const char *actual)
+{
+ for (; *spec == *actual && *spec != '\0'; spec++, actual++)
+ ;
+ return ((*spec == *actual) ||
+ (spec[0] == '*' && spec[1] == '\0'));
+}
diff --git a/fnet/src/vespa/fnet/frt/values.h b/fnet/src/vespa/fnet/frt/values.h
new file mode 100644
index 00000000000..0c3b13b16f3
--- /dev/null
+++ b/fnet/src/vespa/fnet/frt/values.h
@@ -0,0 +1,535 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+template <typename T>
+struct FRT_Array {
+ uint32_t _len;
+ T *_pt;
+};
+
+#define FRT_LPT(type) FRT_Array<type>
+
+enum {
+ FRT_VALUE_NONE = '\0',
+ FRT_VALUE_INT8 = 'b',
+ FRT_VALUE_INT8_ARRAY = 'B',
+ FRT_VALUE_INT16 = 'h',
+ FRT_VALUE_INT16_ARRAY = 'H',
+ FRT_VALUE_INT32 = 'i',
+ FRT_VALUE_INT32_ARRAY = 'I',
+ FRT_VALUE_INT64 = 'l',
+ FRT_VALUE_INT64_ARRAY = 'L',
+ FRT_VALUE_FLOAT = 'f',
+ FRT_VALUE_FLOAT_ARRAY = 'F',
+ FRT_VALUE_DOUBLE = 'd',
+ FRT_VALUE_DOUBLE_ARRAY = 'D',
+ FRT_VALUE_STRING = 's',
+ FRT_VALUE_STRING_ARRAY = 'S',
+ FRT_VALUE_DATA = 'x',
+ FRT_VALUE_DATA_ARRAY = 'X'
+};
+
+
+struct FRT_StringValue
+{
+ uint32_t _len;
+ char *_str;
+};
+
+
+struct FRT_DataValue
+{
+ uint32_t _len;
+ char *_buf;
+};
+
+
+union FRT_Value
+{
+ uint8_t _intval8;
+ FRT_LPT(uint8_t) _int8_array;
+ uint16_t _intval16;
+ FRT_LPT(uint16_t) _int16_array;
+ uint32_t _intval32;
+ FRT_LPT(uint32_t) _int32_array;
+ uint64_t _intval64;
+ FRT_LPT(uint64_t) _int64_array;
+ float _float;
+ FRT_LPT(float) _float_array;
+ double _double;
+ FRT_LPT(double) _double_array;
+ FRT_StringValue _string;
+ FRT_LPT(FRT_StringValue) _string_array;
+ FRT_DataValue _data;
+ FRT_LPT(FRT_DataValue) _data_array;
+};
+
+
+class FRT_Values
+{
+public:
+ class LocalBlob : public FRT_ISharedBlob
+ {
+ public:
+ LocalBlob(vespalib::DefaultAlloc data, uint32_t len) :
+ _data(std::move(data)),
+ _len(len)
+ { }
+ LocalBlob(const char *data, uint32_t len) :
+ _data(len),
+ _len(len)
+ {
+ if (data != NULL) {
+ memcpy(_data.get(), data, len);
+ }
+ }
+ void addRef() override {}
+ void subRef() override { vespalib::DefaultAlloc().swap(_data); }
+ uint32_t getLen() override { return _len; }
+ const char *getData() override { return static_cast<const char *>(_data.get()); }
+ char *getInternalData() { return static_cast<char *>(_data.get()); }
+ private:
+ LocalBlob(const LocalBlob &);
+ LocalBlob &operator=(const LocalBlob &);
+
+ vespalib::DefaultAlloc _data;
+ uint32_t _len;
+ };
+
+ struct BlobRef
+ {
+ FRT_DataValue *_value; // for blob inside data array
+ uint32_t _idx; // for blob as single data value
+ FRT_ISharedBlob *_blob; // interface to shared data
+ BlobRef *_next; // next in list
+
+ BlobRef(FRT_DataValue *value, uint32_t idx, FRT_ISharedBlob *blob, BlobRef *next)
+ : _value(value), _idx(idx), _blob(blob), _next(next) { blob->addRef(); }
+ ~BlobRef() { _blob->subRef(); }
+ private:
+ BlobRef(const BlobRef &);
+ BlobRef &operator=(const BlobRef &);
+ };
+
+private:
+ uint32_t _maxValues;
+ uint32_t _numValues;
+ char *_typeString;
+ FRT_Value *_values;
+ BlobRef *_blobs;
+ FRT_MemoryTub *_tub;
+
+ FRT_Values(const FRT_Values &);
+ FRT_Values &operator=(const FRT_Values &);
+
+public:
+ FRT_Values(FRT_MemoryTub *tub)
+ : _maxValues(0),
+ _numValues(0),
+ _typeString(NULL),
+ _values(NULL),
+ _blobs(NULL),
+ _tub(tub)
+ {
+ assert(sizeof(uint8_t) == 1);
+ assert(sizeof(float) == sizeof(uint32_t));
+ assert(sizeof(double) == sizeof(uint64_t));
+ }
+
+ void DiscardBlobs() {
+ while (_blobs != NULL) {
+ BlobRef *ref = _blobs;
+ _blobs = ref->_next;
+ FRT_ISharedBlob *blob = ref->_blob;
+ FRT_DataValue *value = ref->_value;
+ if (value == NULL) {
+ uint32_t idx = ref->_idx;
+ assert(_numValues > idx);
+ assert(_typeString[idx] == 'x');
+ value = &_values[idx]._data;
+ }
+ if ((value->_buf == blob->getData()) && (value->_len == blob->getLen())) {
+ value->_buf = NULL;
+ value->_len = 0;
+ }
+ ref->~BlobRef();
+ }
+ }
+
+ void Reset()
+ {
+ DiscardBlobs();
+ _maxValues = 0;
+ _numValues = 0;
+ _typeString = NULL;
+ _values = NULL;
+ }
+
+ void EnsureFree(uint32_t need = 1)
+ {
+ if (_numValues + need <= _maxValues)
+ return;
+
+ uint32_t cnt = _maxValues * 2;
+ if (cnt < _numValues + need)
+ cnt = _numValues + need;
+ if (cnt < 16)
+ cnt = 16;
+
+ char *types = (char *) _tub->Alloc(cnt + 1);
+ memcpy(types, _typeString, _numValues);
+ memset(types + _numValues, FRT_VALUE_NONE, cnt + 1 - _numValues);
+ FRT_Value *values = (FRT_Value *) _tub->Alloc(cnt * sizeof(FRT_Value));
+ memcpy(values, _values, _numValues * sizeof(FRT_Value));
+ _maxValues = cnt;
+ _typeString = types;
+ _values = values;
+ }
+
+ void AddInt8(uint8_t value)
+ {
+ EnsureFree();
+ _values[_numValues]._intval8 = value;
+ _typeString[_numValues++] = FRT_VALUE_INT8;
+ }
+
+ uint8_t *AddInt8Array(uint32_t len)
+ {
+ EnsureFree();
+ uint8_t *ret = (uint8_t *) _tub->Alloc(len * sizeof(uint8_t));
+ _values[_numValues]._int8_array._pt = ret;
+ _values[_numValues]._int8_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT8_ARRAY;
+ return ret;
+ }
+
+ void AddInt8Array(const uint8_t *array, uint32_t len)
+ {
+ EnsureFree();
+ uint8_t *pt = (uint8_t *) _tub->Alloc(len * sizeof(uint8_t));
+ _values[_numValues]._int8_array._pt = pt;
+ _values[_numValues]._int8_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT8_ARRAY;
+ memcpy(pt, array, len * sizeof(uint8_t));
+ }
+
+ void AddInt8ArrayRef(uint8_t *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._int8_array._pt = array;
+ _values[_numValues]._int8_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT8_ARRAY;
+ }
+
+ void AddInt16(uint16_t value)
+ {
+ EnsureFree();
+ _values[_numValues]._intval16 = value;
+ _typeString[_numValues++] = FRT_VALUE_INT16;
+ }
+
+ uint16_t *AddInt16Array(uint32_t len)
+ {
+ EnsureFree();
+ uint16_t *ret = (uint16_t *) _tub->Alloc(len * sizeof(uint16_t));
+ _values[_numValues]._int16_array._pt = ret;
+ _values[_numValues]._int16_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT16_ARRAY;
+ return ret;
+ }
+
+ void AddInt16Array(const uint16_t *array, uint32_t len)
+ {
+ EnsureFree();
+ uint16_t *pt = (uint16_t *) _tub->Alloc(len * sizeof(uint16_t));
+ _values[_numValues]._int16_array._pt = pt;
+ _values[_numValues]._int16_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT16_ARRAY;
+ memcpy(pt, array, len * sizeof(uint16_t));
+ }
+
+ void AddInt16ArrayRef(uint16_t *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._int16_array._pt = array;
+ _values[_numValues]._int16_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT16_ARRAY;
+ }
+
+ void AddInt32(uint32_t value)
+ {
+ EnsureFree();
+ _values[_numValues]._intval32 = value;
+ _typeString[_numValues++] = FRT_VALUE_INT32;
+ }
+
+ uint32_t *AddInt32Array(uint32_t len)
+ {
+ EnsureFree();
+ uint32_t *ret = (uint32_t *) _tub->Alloc(len * sizeof(uint32_t));
+ _values[_numValues]._int32_array._pt = ret;
+ _values[_numValues]._int32_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT32_ARRAY;
+ return ret;
+ }
+
+ void AddInt32Array(const uint32_t *array, uint32_t len)
+ {
+ EnsureFree();
+ uint32_t *pt = (uint32_t *) _tub->Alloc(len * sizeof(uint32_t));
+ _values[_numValues]._int32_array._pt = pt;
+ _values[_numValues]._int32_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT32_ARRAY;
+ memcpy(pt, array, len * sizeof(uint32_t));
+ }
+
+ void AddInt32ArrayRef(uint32_t *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._int32_array._pt = array;
+ _values[_numValues]._int32_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT32_ARRAY;
+ }
+
+ void AddInt64(uint64_t value)
+ {
+ EnsureFree();
+ _values[_numValues]._intval64 = value;
+ _typeString[_numValues++] = FRT_VALUE_INT64;
+ }
+
+ uint64_t *AddInt64Array(uint32_t len)
+ {
+ EnsureFree();
+ uint64_t *ret = (uint64_t *) _tub->Alloc(len * sizeof(uint64_t));
+ _values[_numValues]._int64_array._pt = ret;
+ _values[_numValues]._int64_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT64_ARRAY;
+ return ret;
+ }
+
+ void AddInt64Array(const uint64_t *array, uint32_t len)
+ {
+ EnsureFree();
+ uint64_t *pt = (uint64_t *) _tub->Alloc(len * sizeof(uint64_t));
+ _values[_numValues]._int64_array._pt = pt;
+ _values[_numValues]._int64_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT64_ARRAY;
+ memcpy(pt, array, len * sizeof(uint64_t));
+ }
+
+ void AddInt64ArrayRef(uint64_t *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._int64_array._pt = array;
+ _values[_numValues]._int64_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_INT64_ARRAY;
+ }
+
+ void AddFloat(float value)
+ {
+ EnsureFree();
+ _values[_numValues]._float = value;
+ _typeString[_numValues++] = FRT_VALUE_FLOAT;
+ }
+
+ float *AddFloatArray(uint32_t len)
+ {
+ EnsureFree();
+ float *ret = (float *) _tub->Alloc(len * sizeof(float));
+ _values[_numValues]._float_array._pt = ret;
+ _values[_numValues]._float_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_FLOAT_ARRAY;
+ return ret;
+ }
+
+ void AddFloatArray(const float *array, uint32_t len)
+ {
+ EnsureFree();
+ float *pt = (float *) _tub->Alloc(len * sizeof(float));
+ _values[_numValues]._float_array._pt = pt;
+ _values[_numValues]._float_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_FLOAT_ARRAY;
+ memcpy(pt, array, len * sizeof(float));
+ }
+
+ void AddFloatArrayRef(float *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._float_array._pt = array;
+ _values[_numValues]._float_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_FLOAT_ARRAY;
+ }
+
+ void AddDouble(double value)
+ {
+ EnsureFree();
+ _values[_numValues]._double = value;
+ _typeString[_numValues++] = FRT_VALUE_DOUBLE;
+ }
+
+ double *AddDoubleArray(uint32_t len)
+ {
+ EnsureFree();
+ double *ret = (double *) _tub->Alloc(len * sizeof(double));
+ _values[_numValues]._double_array._pt = ret;
+ _values[_numValues]._double_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DOUBLE_ARRAY;
+ return ret;
+ }
+
+ void AddDoubleArray(const double *array, uint32_t len)
+ {
+ EnsureFree();
+ double *pt = (double *) _tub->Alloc(len * sizeof(double));
+ _values[_numValues]._double_array._pt = pt;
+ _values[_numValues]._double_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DOUBLE_ARRAY;
+ memcpy(pt, array, len * sizeof(double));
+ }
+
+ void AddDoubleArrayRef(double *array, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._double_array._pt = array;
+ _values[_numValues]._double_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DOUBLE_ARRAY;
+ }
+
+ void AddString(const char *str, uint32_t len)
+ {
+ EnsureFree();
+ _values[_numValues]._string._str = _tub->CopyString(str, len);
+ _values[_numValues]._string._len = len;
+ _typeString[_numValues++] = FRT_VALUE_STRING;
+ }
+
+ void AddString(const char *str)
+ {
+ AddString(str, strlen(str));
+ }
+
+ char *AddString(uint32_t len)
+ {
+ EnsureFree();
+ char *ret = (char *) _tub->Alloc(len + 1);
+ _values[_numValues]._string._str = ret;
+ _values[_numValues]._string._len = len;
+ _typeString[_numValues++] = FRT_VALUE_STRING;
+ return ret;
+ }
+
+ FRT_StringValue *AddStringArray(uint32_t len)
+ {
+ EnsureFree();
+ FRT_StringValue *ret = (FRT_StringValue *) _tub->Alloc(len * sizeof(FRT_StringValue));
+ _values[_numValues]._string_array._pt = ret;
+ _values[_numValues]._string_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_STRING_ARRAY;
+ return ret;
+ }
+
+ void AddSharedData(FRT_ISharedBlob *blob)
+ {
+ EnsureFree();
+ _blobs = new (_tub) BlobRef(NULL, _numValues, blob, _blobs);
+ _values[_numValues]._data._buf = const_cast<char *>(blob->getData());
+ _values[_numValues]._data._len = blob->getLen();
+ _typeString[_numValues++] = FRT_VALUE_DATA;
+ }
+
+ void AddData(vespalib::DefaultAlloc buf, uint32_t len)
+ {
+ AddSharedData(new (_tub) LocalBlob(std::move(buf), len));
+ }
+
+ void AddData(const char *buf, uint32_t len)
+ {
+ if (len > FRT_MemoryTub::ALLOC_LIMIT) {
+ return AddSharedData(new (_tub) LocalBlob(buf, len));
+ }
+ EnsureFree();
+ _values[_numValues]._data._buf = _tub->CopyData(buf, len);
+ _values[_numValues]._data._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DATA;
+ }
+
+ char *AddData(uint32_t len)
+ {
+ if (len > FRT_MemoryTub::ALLOC_LIMIT) {
+ LocalBlob *blob = new (_tub) LocalBlob(NULL, len);
+ AddSharedData(blob);
+ return blob->getInternalData();
+ }
+ EnsureFree();
+ char *ret = (char *) _tub->Alloc(len);
+ _values[_numValues]._data._buf = ret;
+ _values[_numValues]._data._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DATA;
+ return ret;
+ }
+
+ FRT_DataValue *AddDataArray(uint32_t len)
+ {
+ EnsureFree();
+ FRT_DataValue *ret = (FRT_DataValue *) _tub->Alloc(len * sizeof(FRT_DataValue));
+ _values[_numValues]._data_array._pt = ret;
+ _values[_numValues]._data_array._len = len;
+ _typeString[_numValues++] = FRT_VALUE_DATA_ARRAY;
+ return ret;
+ }
+
+ void SetString(FRT_StringValue *value,
+ const char *str, uint32_t len)
+ {
+ value->_str = _tub->CopyString(str, len);
+ value->_len = len;
+ }
+
+ void SetString(FRT_StringValue *value,
+ const char *str)
+ {
+ uint32_t len = strlen(str);
+ value->_str = _tub->CopyString(str, len);
+ value->_len = len;
+ }
+
+ void SetData(FRT_DataValue *value,
+ const char *buf, uint32_t len)
+ {
+ char *mybuf = NULL;
+ if (len > FRT_MemoryTub::ALLOC_LIMIT) {
+ LocalBlob *blob = new (_tub) LocalBlob(buf, len);
+ _blobs = new (_tub) BlobRef(value, 0, blob, _blobs);
+ mybuf = blob->getInternalData();
+ } else {
+ mybuf = _tub->CopyData(buf, len);
+ }
+ value->_buf = mybuf;
+ value->_len = len;
+ }
+
+
+ uint32_t GetNumValues() { return _numValues; }
+ const char *GetTypeString() { return _typeString; }
+ FRT_Value &GetValue(uint32_t idx) { return _values[idx]; }
+ FRT_Value &operator [](uint32_t idx) { return _values[idx]; }
+ const FRT_Value &operator [](uint32_t idx) const { return _values[idx]; }
+ uint32_t GetType(uint32_t idx) { return _typeString[idx]; }
+ void Print(uint32_t indent = 0);
+ uint32_t GetLength();
+ bool DecodeCopy(FNET_DataBuffer *dst, uint32_t len);
+ bool DecodeBig(FNET_DataBuffer *dst, uint32_t len);
+ bool DecodeLittle(FNET_DataBuffer *dst, uint32_t len);
+ void EncodeCopy(FNET_DataBuffer *dst);
+ void EncodeBig(FNET_DataBuffer *dst);
+ bool Equals(FRT_Values *values);
+ static void Print(FRT_Value value, uint32_t type,
+ uint32_t indent = 0);
+ static bool Equals(FRT_Value a, FRT_Value b, uint32_t type);
+ static bool Equals(FRT_Value a, uint32_t a_type,
+ FRT_Value b, uint32_t b_type);
+ static bool CheckTypes(const char *spec, const char *actual);
+};
+
diff --git a/fnet/src/vespa/fnet/iexecutable.h b/fnet/src/vespa/fnet/iexecutable.h
new file mode 100644
index 00000000000..d4a8ebdbb70
--- /dev/null
+++ b/fnet/src/vespa/fnet/iexecutable.h
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Interface used when injecting code execution into the transport
+ * thread.
+ **/
+class FNET_IExecutable
+{
+public:
+ /**
+ * Invoked by the transport thread as the only step to handle an
+ * execution event.
+ **/
+ virtual void execute() = 0;
+
+ /**
+ * empty
+ **/
+ virtual ~FNET_IExecutable() {}
+};
+
diff --git a/fnet/src/vespa/fnet/info.cpp b/fnet/src/vespa/fnet/info.cpp
new file mode 100644
index 00000000000..c345fc2e28c
--- /dev/null
+++ b/fnet/src/vespa/fnet/info.cpp
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+
+
+uint32_t FNET_Info::_endian = FNET_Info::ENDIAN_UNKNOWN;
+FNET_Info global_fnet_info_object;
+
+
+FNET_Info::FNET_Info()
+{
+ uint8_t *pt = NULL;
+ uint64_t cmp = 0;
+ uint32_t endian = ENDIAN_UNKNOWN;
+
+ uint16_t intval16;
+ uint32_t intval32;
+ uint64_t intval64;
+
+ pt = (uint8_t *) &intval16;
+ pt[0] = 1;
+ pt[1] = 2;
+
+ pt = (uint8_t *) &intval32;
+ pt[0] = 1;
+ pt[1] = 2;
+ pt[2] = 3;
+ pt[3] = 4;
+
+ pt = (uint8_t *) &intval64;
+ pt[0] = 1;
+ pt[1] = 2;
+ pt[2] = 3;
+ pt[3] = 4;
+ pt[4] = 5;
+ pt[5] = 6;
+ pt[6] = 7;
+ pt[7] = 8;
+
+ cmp = 0x08070605;
+ cmp = (cmp << 32) + 0x04030201;
+ if (intval16 == 0x0201 &&
+ intval32 == 0x04030201 &&
+ intval64 == cmp)
+ endian = ENDIAN_LITTLE;
+
+ cmp = 0x01020304;
+ cmp = (cmp << 32) + 0x05060708;
+ if (intval16 == 0x0102 &&
+ intval32 == 0x01020304 &&
+ intval64 == cmp)
+ endian = ENDIAN_BIG;
+
+ _endian = endian;
+}
+
+
+const char*
+FNET_Info::GetFNETVersion()
+{
+ return fnet::VersionTag;
+}
+
+
+void
+FNET_Info::PrintInfo()
+{
+ printf("This method is deprecated. "
+ "Use the FNET_Info::LogInfo method instead.\n");
+}
+
+
+void
+FNET_Info::LogInfo()
+{
+ LOG(info, "FNET Version : %s", GetFNETVersion());
+ const char *endian_str = "UNKNOWN";
+ if (_endian == ENDIAN_LITTLE)
+ endian_str = "LITTLE";
+ if (_endian == ENDIAN_BIG)
+ endian_str = "BIG";
+ LOG(info, "Host Endian : %s", endian_str);
+ const char *thread_str = HasThreads() ? "true" : "false";
+ LOG(info, "Thread support : %s", thread_str);
+}
diff --git a/fnet/src/vespa/fnet/info.h b/fnet/src/vespa/fnet/info.h
new file mode 100644
index 00000000000..3ac46d853e0
--- /dev/null
+++ b/fnet/src/vespa/fnet/info.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class provides overall information about the FNET
+ * implementation.
+ **/
+class FNET_Info
+{
+public:
+ /**
+ * Host endian enum. See @ref GetEndian method below.
+ **/
+ enum {
+ ENDIAN_UNKNOWN,
+ ENDIAN_LITTLE,
+ ENDIAN_BIG
+ };
+
+private:
+ static uint32_t _endian;
+
+public:
+ /**
+ * A static instance of the FNET_Info class is used to ensure that
+ * this method is run (once) on application startup. It performs
+ * some probing to obtain information about the host.
+ **/
+ FNET_Info();
+
+ /**
+ * @return true if we have support for threads
+ **/
+ static bool HasThreads() { return FNET_HAS_THREADS; }
+
+ /**
+ * @return the host endian (unknown/little/big)
+ **/
+ static uint32_t GetEndian() { return _endian; }
+
+ /**
+ * This method may be used to obtain a string describing the
+ * FNET version.
+ *
+ * @return a string indicating the FNET version.
+ **/
+ static const char *GetFNETVersion();
+
+ /**
+ * This method is deprecated. Use the FNET_Info::LogInfo method
+ * instead.
+ **/
+ static void PrintInfo();
+
+ /**
+ * Invoking this method logs various information about FNET.
+ **/
+ static void LogInfo();
+};
+
diff --git a/fnet/src/vespa/fnet/iocomponent.cpp b/fnet/src/vespa/fnet/iocomponent.cpp
new file mode 100644
index 00000000000..b524298a121
--- /dev/null
+++ b/fnet/src/vespa/fnet/iocomponent.cpp
@@ -0,0 +1,125 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+FNET_IOComponent::FNET_IOComponent(FNET_TransportThread *owner,
+ FastOS_Socket *mysocket,
+ const char *spec,
+ bool shouldTimeOut)
+ : _ioc_next(NULL),
+ _ioc_prev(NULL),
+ _ioc_owner(owner),
+ _ioc_counters(_ioc_owner->GetStatCounters()),
+ _ioc_socket(mysocket),
+ _ioc_spec(NULL),
+ _flags(shouldTimeOut),
+ _ioc_timestamp(fastos::ClockSystem::now()),
+ _ioc_cond(),
+ _ioc_refcnt(1),
+ _ioc_directPacketWriteCnt(0),
+ _ioc_directDataWriteCnt(0)
+{
+ _ioc_spec = strdup(spec);
+ assert(_ioc_spec != NULL);
+}
+
+
+FNET_IOComponent::~FNET_IOComponent()
+{
+ free(_ioc_spec);
+}
+
+
+void
+FNET_IOComponent::AddRef()
+{
+ Lock();
+ assert(_ioc_refcnt > 0);
+ _ioc_refcnt++;
+ Unlock();
+}
+
+
+void
+FNET_IOComponent::AddRef_NoLock()
+{
+ assert(_ioc_refcnt > 0);
+ _ioc_refcnt++;
+}
+
+
+void
+FNET_IOComponent::SubRef()
+{
+ Lock();
+ assert(_ioc_refcnt > 0);
+ if (--_ioc_refcnt > 0) {
+ Unlock();
+ return;
+ }
+ Unlock();
+ CleanupHook();
+ delete this;
+}
+
+
+void
+FNET_IOComponent::SubRef_HasLock()
+{
+ assert(_ioc_refcnt > 0);
+ if (--_ioc_refcnt > 0) {
+ Unlock();
+ return;
+ }
+ Unlock();
+ CleanupHook();
+ delete this;
+}
+
+
+void
+FNET_IOComponent::SubRef_NoLock()
+{
+ assert(_ioc_refcnt > 1);
+ _ioc_refcnt--;
+}
+
+
+void
+FNET_IOComponent::SetSocketEvent(FastOS_SocketEvent *event)
+{
+ bool rc = _ioc_socket->SetSocketEvent(event, this);
+ assert(rc); // XXX: error handling
+ (void) rc;
+
+ if (event != NULL) {
+ _ioc_socket->EnableReadEvent(_flags._ioc_readEnabled);
+ _ioc_socket->EnableWriteEvent(_flags._ioc_writeEnabled);
+ }
+}
+
+
+void
+FNET_IOComponent::EnableReadEvent(bool enabled)
+{
+ _flags._ioc_readEnabled = enabled;
+ if (_ioc_socket->GetSocketEvent() != NULL)
+ _ioc_socket->EnableReadEvent(enabled);
+}
+
+
+void
+FNET_IOComponent::EnableWriteEvent(bool enabled)
+{
+ _flags._ioc_writeEnabled = enabled;
+ if (_ioc_socket->GetSocketEvent() != NULL)
+ _ioc_socket->EnableWriteEvent(enabled);
+}
+
+
+void
+FNET_IOComponent::CleanupHook()
+{
+}
diff --git a/fnet/src/vespa/fnet/iocomponent.h b/fnet/src/vespa/fnet/iocomponent.h
new file mode 100644
index 00000000000..33075657372
--- /dev/null
+++ b/fnet/src/vespa/fnet/iocomponent.h
@@ -0,0 +1,324 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This is the common superclass of all components that may be part of
+ * the transport layer event based I/O framework. Note that all IO
+ * Components do IO against the network and that they use sockets to
+ * perform that IO.
+ **/
+class FNET_IOComponent
+{
+ friend class FNET_TransportThread;
+
+ FNET_IOComponent(const FNET_IOComponent &);
+ FNET_IOComponent &operator=(const FNET_IOComponent &);
+
+ struct Flags {
+ Flags(bool shouldTimeout) :
+ _ioc_readEnabled(false),
+ _ioc_writeEnabled(false),
+ _ioc_shouldTimeOut(shouldTimeout),
+ _ioc_added(false),
+ _ioc_delete(false),
+ _ioc_want_close(false)
+ { }
+ bool _ioc_readEnabled; // read event enabled ?
+ bool _ioc_writeEnabled; // write event enabled ?
+ bool _ioc_shouldTimeOut; // component should timeout ?
+ bool _ioc_added; // was added to event loop
+ bool _ioc_delete; // going down...
+ bool _ioc_want_close; // closed while not added to event loop
+ };
+protected:
+ FNET_IOComponent *_ioc_next; // next in list
+ FNET_IOComponent *_ioc_prev; // prev in list
+ FNET_TransportThread *_ioc_owner; // owner(TransportThread) ref.
+ FNET_StatCounters *_ioc_counters; // stat counters
+ FastOS_Socket *_ioc_socket; // source of events.
+ char *_ioc_spec; // connect/listen spec
+ Flags _flags; // Compressed representation of boolean flags;
+ fastos::TimeStamp _ioc_timestamp; // last I/O activity
+ FNET_Cond _ioc_cond; // synchronization
+ uint32_t _ioc_refcnt; // reference counter
+
+ // direct write stats kept locally
+ uint32_t _ioc_directPacketWriteCnt;
+ uint32_t _ioc_directDataWriteCnt;
+
+public:
+
+ /**
+ * Construct an IOComponent with the given owner. The socket that
+ * will be used for IO is also given. The reason for this is to
+ * enable the IOC superclass to handle all event registration and
+ * deregistration without having to rely on code located in
+ * subclasses.
+ *
+ * @param owner the TransportThread object owning this component
+ * @param mysocket the socket used by this IOC
+ * @param spec listen/connect spec for this IOC
+ * @param shouldTimeOut should this IOC time out if idle ?
+ **/
+ FNET_IOComponent(FNET_TransportThread *owner, FastOS_Socket *mysocket,
+ const char *spec, bool shouldTimeOut);
+
+
+ /**
+ * Destruct component.
+ **/
+ virtual ~FNET_IOComponent();
+
+
+ /**
+ * @return connect/listen spec
+ **/
+ const char *GetSpec() const { return _ioc_spec; }
+
+
+ /**
+ * Lock object to gain exclusive access.
+ **/
+ void Lock() { _ioc_cond.Lock(); }
+
+
+ /**
+ * Unlock object to yield exclusive access.
+ **/
+ void Unlock() { _ioc_cond.Unlock(); }
+
+
+ /**
+ * Wait on this object. Caller should have lock on object.
+ **/
+ void Wait() { _ioc_cond.Wait(); }
+
+
+ /**
+ * Signal one thread waiting on this object. Caller should have
+ * lock.
+ **/
+ void Signal() { _ioc_cond.Signal(); }
+
+
+ /**
+ * Signal all thread waiting on this object. Caller should have
+ * lock.
+ **/
+ void Broadcast() { _ioc_cond.Broadcast(); }
+
+
+ /**
+ * Allocate a reference to this component. This method locks the
+ * object to protect the reference counter.
+ **/
+ void AddRef();
+
+
+ /**
+ * Allocate a reference to this component without locking the
+ * object. Caller already has lock on object.
+ **/
+ void AddRef_NoLock();
+
+
+ /**
+ * Free a reference to this component. This method locks the object
+ * to protect the reference counter.
+ **/
+ void SubRef();
+
+
+ /**
+ * Free a reference to this component. This method uses locking to
+ * protect the reference counter, but assumes that the lock has
+ * already been obtained when this method is called.
+ **/
+ void SubRef_HasLock();
+
+
+ /**
+ * Free a reference to this component without locking the
+ * object. NOTE: this method may only be called on objects with more
+ * than one reference.
+ **/
+ void SubRef_NoLock();
+
+
+ /**
+ * @return the owning TransportThread object.
+ **/
+ FNET_TransportThread *Owner() { return _ioc_owner; }
+
+
+ /**
+ * Get the configuration object associated with the owning transport
+ * object.
+ *
+ * @return config object.
+ **/
+ FNET_Config *GetConfig() { return _ioc_owner->GetConfig(); }
+
+
+ /**
+ * @return whether this component should time-out if idle.
+ **/
+ bool ShouldTimeOut() { return _flags._ioc_shouldTimeOut; }
+
+
+ /**
+ * Update time-out information. This method simply performs a
+ * proxy-call to the owning transport object, calling
+ * FNET_TransportThread::UpdateTimeOut() with itself as parameter.
+ **/
+ void UpdateTimeOut() { _ioc_owner->UpdateTimeOut(this); }
+
+
+ /**
+ * Count packet read(s). This is a proxy method updating the stat
+ * counters associated with the owning transport object.
+ *
+ * @param cnt the number of packets read (default is 1).
+ **/
+ void CountPacketRead(uint32_t cnt = 1)
+ { _ioc_counters->CountPacketRead(cnt); }
+
+
+ /**
+ * Count packet write(s). This is a proxy method updating the stat
+ * counters associated with the owning transport object.
+ *
+ * @param cnt the number of packets written (default is 1).
+ **/
+ void CountPacketWrite(uint32_t cnt = 1)
+ { _ioc_counters->CountPacketWrite(cnt); }
+
+
+ /**
+ * Count direct packet write(s). This method will increase an
+ * internal counter. The shared stat counters may not be used
+ * because this method may be called by other threads than the
+ * transport thread. Note: The IO Component should be locked when
+ * this method is called.
+ *
+ * @param cnt the number of packets written (default is 1).
+ **/
+ void CountDirectPacketWrite(uint32_t cnt = 1)
+ { _ioc_directPacketWriteCnt += cnt; }
+
+
+ /**
+ * Count read data. This is a proxy method updating the stat
+ * counters associated with the owning transport object.
+ *
+ * @param bytes the number of bytes read.
+ **/
+ void CountDataRead(uint32_t bytes)
+ { _ioc_counters->CountDataRead(bytes); }
+
+
+ /**
+ * Count written data. This is a proxy method updating the stat
+ * counters associated with the owning transport object.
+ *
+ * @param bytes the number of bytes written.
+ **/
+ void CountDataWrite(uint32_t bytes)
+ { _ioc_counters->CountDataWrite(bytes); }
+
+
+ /**
+ * Count direct written data. This method will increase an
+ * internal counter. The shared stat counters may not be used
+ * because this method may be called by other threads than the
+ * transport thread. Note: The IO Component should be locked when
+ * this method is called.
+ *
+ * @param bytes the number of bytes written.
+ **/
+ void CountDirectDataWrite(uint32_t bytes)
+ { _ioc_directDataWriteCnt += bytes; }
+
+
+ /**
+ * Transfer the direct write stats held by this IO Component over to
+ * the stat counters associated with the owning transport object
+ * (and reset the local counters). Note: This method should only be
+ * called from the transport thread while having the lock on this IO
+ * Component. Note: This method is called from the transport loop
+ * and should generally not be called by application code.
+ **/
+ void FlushDirectWriteStats()
+ {
+ _ioc_counters->CountPacketWrite(_ioc_directPacketWriteCnt);
+ _ioc_counters->CountDataWrite(_ioc_directDataWriteCnt);
+ _ioc_directPacketWriteCnt = 0;
+ _ioc_directDataWriteCnt = 0;
+ }
+
+
+ /**
+ * Assign a FastOS_SocketEvent to this component. Before deleting an
+ * IOC, one must assign NULL as the socket event.
+ *
+ * @param event the socket event to register with.
+ **/
+ void SetSocketEvent(FastOS_SocketEvent *event);
+
+
+ /**
+ * Enable or disable read events.
+ *
+ * @param enabled enabled(true)/disabled(false).
+ **/
+ void EnableReadEvent(bool enabled);
+
+
+ /**
+ * Enable or disable write events.
+ *
+ * @param enabled enabled(true)/disabled(false).
+ **/
+ void EnableWriteEvent(bool enabled);
+
+
+ //----------- virtual methods below ----------------------//
+
+
+ /**
+ * This method is called by the SubRef methods just before the
+ * object is deleted. It may be used to perform cleanup tasks that
+ * must be done before the destructor is invoked.
+ **/
+ virtual void CleanupHook();
+
+
+ /**
+ * Close this component immediately. NOTE: this method should only
+ * be called by the transport thread. If you want to close an IO
+ * Component from another thread you should use the
+ * FNET_TransportThread::Close method instead (with the IOC you want to
+ * close as parameter).
+ **/
+ virtual void Close() = 0;
+
+
+ /**
+ * Called by the transport thread when a read event has
+ * occurred.
+ *
+ * @return false if broken, true otherwise.
+ **/
+ virtual bool HandleReadEvent() = 0;
+
+
+ /**
+ * Called by the transport thread when a write event has
+ * occurred.
+ *
+ * @return false if broken, true otherwise.
+ **/
+ virtual bool HandleWriteEvent() = 0;
+};
+
diff --git a/fnet/src/vespa/fnet/ipacketfactory.h b/fnet/src/vespa/fnet/ipacketfactory.h
new file mode 100644
index 00000000000..39d3601456d
--- /dev/null
+++ b/fnet/src/vespa/fnet/ipacketfactory.h
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Interface describing objects that are able to create packets. An
+ * object implementing this interface is needed in order to use the
+ * SimplePacketStreamer class.
+ **/
+class FNET_IPacketFactory
+{
+public:
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FNET_IPacketFactory(void) { }
+
+ /**
+ * Create a packet.
+ *
+ * @return the newly created packet.
+ * @param pcode what kind of packet to create. The semantic of this
+ * field is left to the implementing object to allow
+ * parallell dimensions of packet types in the same
+ * application.
+ * @param context application context. When this class is used by
+ * the SimplePacketStreamer, this is the application context
+ * for the channel that will receive the packet after it is
+ * created and un-streamed.
+ **/
+ virtual FNET_Packet *CreatePacket(uint32_t pcode,
+ FNET_Context context) = 0;
+};
+
diff --git a/fnet/src/vespa/fnet/ipackethandler.h b/fnet/src/vespa/fnet/ipackethandler.h
new file mode 100644
index 00000000000..b0af1ba0484
--- /dev/null
+++ b/fnet/src/vespa/fnet/ipackethandler.h
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Interface implemented by objects that can handle packets.
+ **/
+class FNET_IPacketHandler
+{
+public:
+
+ /**
+ * This enum defines the possible values returned from the @ref
+ * HandlePacket method. The @ref HandlePacket method is called on
+ * the packet handler registered as the end-point of a channel when
+ * a packet is received on that channel. The return value tells FNET
+ * what to do with the channel; keep it open, close it or free
+ * it. If the channel is closed, no more packets will be delivered
+ * from FNET on that channel. The application however, may still use
+ * a closed channel to send packets. If the channel is freed, it
+ * will be closed in both directions and may not be used by the
+ * application.
+ **/
+ enum HP_RetCode {
+ FNET_KEEP_CHANNEL = 0,
+ FNET_CLOSE_CHANNEL = 1,
+ FNET_FREE_CHANNEL = 2
+ };
+
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FNET_IPacketHandler(void) { }
+
+ /**
+ * Handle an incoming packet in the given context. All incoming
+ * packets are received through some channel. The application should
+ * assign appropriate contexts to the different channels in order to
+ * differentiate between them. Due to thread-restrictions the
+ * channel on which a packet was received may not be closed during
+ * the HandlePacket callback. However, the return code of this
+ * method may tell FNET to keep the channel open, to close the
+ * channel or to free the channel (freeing the channel implicitly
+ * closes it first). NOTE: packet handover (caller TO invoked
+ * object).
+ *
+ * @return channel command: keep open, close or free.
+ * @param packet the packet to handle.
+ * @param context the application context for the packet.
+ **/
+ virtual HP_RetCode HandlePacket(FNET_Packet *packet,
+ FNET_Context context) = 0;
+};
+
diff --git a/fnet/src/vespa/fnet/ipacketstreamer.h b/fnet/src/vespa/fnet/ipacketstreamer.h
new file mode 100644
index 00000000000..734ec74da8c
--- /dev/null
+++ b/fnet/src/vespa/fnet/ipacketstreamer.h
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Class used to do custom streaming of packets on network
+ * connections. The application is responsible for implementing the
+ * functionality of the packet streamer. It is recommended that it is
+ * backed by a packet factory object.
+ **/
+class FNET_IPacketStreamer
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FNET_IPacketStreamer(void) { }
+
+ /**
+ * This method is called to obtain information about the next packet
+ * located in the databuffer. The information obtained by calling
+ * this method is used to resolve the application context of the
+ * channel that should receive the packet and to ensure that the
+ * entire packet is read into the databuffer before the @ref Decode
+ * method is invoked. If this method returns true, the 'plen' output
+ * value will contain the number of bytes required to be located in
+ * the databuffer before the @ref Decode method is invoked. This
+ * method is also the place for packet header syncing, as it is
+ * allowed to discard data from the source databuffer. If this
+ * method returns false, it should be called again after more data
+ * is read into the source databuffer.
+ *
+ * If the contents of the source databuffer is not a valid packet
+ * header, the 'broken' flag may be raised to indicate that the
+ * connection should be closed due to illegal data being sent.
+ *
+ * @return true on success/false on fail
+ * @param src databuffer to read packet information from
+ * @param plen where to store packet length
+ * @param pcode where to store the packet code
+ * @param chid where to store the packet chid
+ * @param broken where to signal broken data
+ **/
+ virtual bool GetPacketInfo(FNET_DataBuffer *src, uint32_t *plen,
+ uint32_t *pcode, uint32_t *chid,
+ bool *broken) = 0;
+
+ /**
+ * This method is called to un-stream a packet from the given
+ * databuffer. This method will only be called after a call to the
+ * @ref GetPacketInfo returns true and a number of bytes equal the
+ * packet size indicated by that method is available in the
+ * databuffer. The context of the channel that will receive the
+ * packet is given as a parameter to this method in order to allow
+ * application-layer customizations such as using memory pools. The
+ * packet length and packet code output values from the @ref
+ * GetPacketInfo method are given as parameters to this method to
+ * avoid the need to parse the packet header twice.
+ *
+ * @return packet decoded from 'buf' or NULL on failure
+ * @param src buffer with the serialized packet
+ * @param plen packet length as reported by @ref GetPacketInfo
+ * @param pcode packet code as reported by @ref GetPacketInfo
+ * @param context application context for target channel
+ **/
+ virtual FNET_Packet *Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t pcode, FNET_Context context) = 0;
+
+ /**
+ * This method is called to stream a packet to the given databuffer.
+ *
+ * @param packet the packet to stream
+ * @param chid channel id for packet
+ * @param dst the target buffer for streaming
+ **/
+ virtual void Encode(FNET_Packet *packet, uint32_t chid,
+ FNET_DataBuffer *dst) = 0;
+};
+
diff --git a/fnet/src/vespa/fnet/iserveradapter.h b/fnet/src/vespa/fnet/iserveradapter.h
new file mode 100644
index 00000000000..3de01251392
--- /dev/null
+++ b/fnet/src/vespa/fnet/iserveradapter.h
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class must be extended by the server application. It is needed
+ * to let the application define the target packet handler for
+ * incoming channels without creating a race condition.
+ **/
+class FNET_IServerAdapter
+{
+public:
+
+ /**
+ * Destructor. No cleanup needed for base class.
+ */
+ virtual ~FNET_IServerAdapter(void) { }
+
+ /**
+ * This method is called by the network layer when an incoming
+ * connection has been accepted. It gives the application a chance
+ * to define the target packet handler and application context for
+ * incoming admin packets. All packets received with the reserved
+ * channel id (FNET_NOID) are considered admin packets.
+ *
+ * In order to return true from this method both the handler and
+ * context must be set for the given channel object.
+ *
+ * NOTE: Generally, application code should never close a connection
+ * by invoking the Close method directly. However, as this method is
+ * invoked by the transport thread before the connection is added to
+ * the event-loop framework, the Close method on the incoming
+ * connection may be invoked by this method. This may be useful for
+ * limiting the number of allowed concurrent connections. NOTE: if
+ * the incoming connection is closed, this method MUST NOT return
+ * true!
+ *
+ * @return success(true)/fail(false)
+ * @param channel the admin channel being initialized.
+ **/
+ virtual bool InitAdminChannel(FNET_Channel *channel) = 0;
+
+
+ /**
+ * This method is called by the network layer when opening a new
+ * channel on a connection handled by this server adapter. The
+ * implementation of this method must define the target packet
+ * handler and the application context for the given channel. The
+ * 'pcode' parameter indicates the type of the first packet to be
+ * received on this channel.
+ *
+ * @return success(true)/fail(false)
+ * @param channel the channel being initialized.
+ * @param pcode the packet type of the first packet on the channel.
+ **/
+ virtual bool InitChannel(FNET_Channel *channel,
+ uint32_t pcode) = 0;
+};
+
diff --git a/fnet/src/vespa/fnet/packet.cpp b/fnet/src/vespa/fnet/packet.cpp
new file mode 100644
index 00000000000..c61ae2dc2a4
--- /dev/null
+++ b/fnet/src/vespa/fnet/packet.cpp
@@ -0,0 +1,16 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/stringfmt.h>
+
+
+vespalib::string
+FNET_Packet::Print(uint32_t indent)
+{
+ return vespalib::make_string("%*sFNET_Packet[subclass] { regular=%s, control=%s, "
+ "pcode=%d, command=%d, length=%d }\n", indent, "",
+ IsRegularPacket()? "true" : "false",
+ IsControlPacket()? "true" : "false",
+ GetPCODE(), GetCommand(), GetLength());
+}
diff --git a/fnet/src/vespa/fnet/packet.h b/fnet/src/vespa/fnet/packet.h
new file mode 100644
index 00000000000..a7033b8008f
--- /dev/null
+++ b/fnet/src/vespa/fnet/packet.h
@@ -0,0 +1,156 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/stllike/string.h>
+
+/**
+ * This is a general superclass of all packets. Packets are used to
+ * encapsulate data when communicating with other computers through
+ * the network layer, or with the network layer itself. A packet may
+ * be encoded into a byte stream representation held by a DataBuffer
+ * object. The content of a DataBuffer may also be decoded into packet
+ * member variables.
+ **/
+class FNET_Packet
+{
+public:
+ typedef std::unique_ptr<FNET_Packet> UP;
+ typedef std::shared_ptr<FNET_Packet> SP;
+
+ /** Does nothing. **/
+ FNET_Packet() {}
+
+ /** Does nothing. **/
+ virtual ~FNET_Packet() {}
+
+
+ /**
+ * This method is called to indicate that there is no more need for
+ * this packet. In the FNET_Packet class this method is simply
+ * implemented by deleting the packet object. Subclasses may
+ * override this method to implement mechanisms like packet sharing
+ * and/or pooling.
+ **/
+ virtual void Free() { delete this; }
+
+
+ /**
+ * Check if this is a regular packet. A regular packet may be
+ * encoded into a DataBuffer and sent accross the network. Regular
+ * packet implementations do not need to override this method.
+ *
+ * @return whether this is a regular packet (true)
+ **/
+ virtual bool IsRegularPacket() { return true; }
+
+
+ /**
+ * Check if this is a control packet. A control packet is a special
+ * kind of packet used to report events in FNET. See the @ref
+ * FNET_ControlPacket class for more information. Regular packet
+ * implementations do not need to override this method.
+ *
+ * @return whether this is a control packet (false)
+ **/
+ virtual bool IsControlPacket() { return false; }
+
+
+ /**
+ * Method used to extract the command associated with this
+ * packet. Packets that let the @ref IsControlPacket method return
+ * false should always let this method return 0 (no command). See
+ * the @ref FNET_ControlPacket class for more information. Regular
+ * packet implementations do not need to override this method.
+ *
+ * @return packet command (0)
+ **/
+ virtual uint32_t GetCommand() { return 0; }
+
+
+ /**
+ * Convenience method used to check whether this packet is a control
+ * packet signaling the loss of a channel. This method should return
+ * true if and only if the @ref IsControlPacket method returns true
+ * and the @ref GetCommand method returns
+ * FNET_ControlPacket::FNET_CMD_CHANNEL_LOST. Regular packet
+ * implementations do not need to override this method.
+ *
+ * @return is this a channel lost packet ? (false)
+ **/
+ virtual bool IsChannelLostCMD() { return false; }
+
+
+ /**
+ * Convenience method used to check whether this packet is a control
+ * packet signaling a timeout. This method should return true if and
+ * only if the @ref IsControlPacket method returns true and the @ref
+ * GetCommand method returns
+ * FNET_ControlPacket::FNET_CMD_TIMEOUT. Regular packet
+ * implementations do not need to override this method. Note that
+ * FNET does not use timeout packets internally. They are only
+ * included to easy the implementation of timeout signaling in
+ * applications using FNET.
+ *
+ * @return is this a timeout packet ? (false)
+ **/
+ virtual bool IsTimeoutCMD() { return false; }
+
+
+ /**
+ * Convenience method used to check whether this packet is a control
+ * packet signaling a bad packet. This method should return true if
+ * and only if the @ref IsControlPacket method returns true and the
+ * @ref GetCommand method returns
+ * FNET_ControlPacket::FNET_CMD_BAD_PACKET. Regular packet
+ * implementations do not need to override this method. Whenever an
+ * incoming packet may not be decoded from the network stream
+ * (packet format protocol error), a bad packet control packet is
+ * delivered instead.
+ *
+ * @return is this a badpacket packet ? (false)
+ **/
+ virtual bool IsBadPacketCMD() { return false; }
+
+
+ /**
+ * @return the packet code for this packet.
+ **/
+ virtual uint32_t GetPCODE() = 0;
+
+
+ /**
+ * @return encoded packet length in bytes
+ **/
+ virtual uint32_t GetLength() = 0;
+
+
+ /**
+ * Encode this packet into a DataBuffer. This method may only be
+ * called on regular packets. See @ref IsRegularPacket.
+ *
+ * @param dst the target databuffer
+ **/
+ virtual void Encode(FNET_DataBuffer *dst) = 0;
+
+
+ /**
+ * Decode data from the given DataBuffer and store that information
+ * in this object. This method may only be called on regular
+ * packets. See @ref IsRegularPacket.
+ *
+ * @return true on success, false otherwise
+ * @param src the data source
+ * @param len length of the streamed representation
+ **/
+ virtual bool Decode(FNET_DataBuffer *src, uint32_t len) = 0;
+
+
+ /**
+ * Print a textual representation of this packet to stdout. This
+ * method is used for debugging purposes.
+ *
+ * @param indent indent in number of spaces
+ **/
+ virtual vespalib::string Print(uint32_t indent = 0);
+};
diff --git a/fnet/src/vespa/fnet/packetqueue.cpp b/fnet/src/vespa/fnet/packetqueue.cpp
new file mode 100644
index 00000000000..1a2bd92fd96
--- /dev/null
+++ b/fnet/src/vespa/fnet/packetqueue.cpp
@@ -0,0 +1,276 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+void
+FNET_PacketQueue_NoLock::ExpandBuf(uint32_t needentries)
+{
+ uint32_t oldsize = _bufsize;
+ if (_bufsize < 8)
+ _bufsize = 8;
+ while (_bufsize < _bufused + needentries)
+ _bufsize *= 2;
+ _QElem *newbuf = static_cast<_QElem *>(malloc(sizeof(_QElem) * _bufsize));
+ assert(newbuf != NULL);
+ if (_bufused == 0) { // EMPTY
+ // BUFFER: |....................|
+ // USED: |....................|
+
+ } else if (_in_pos > _out_pos) { // NON-WRAPPED
+ // BUFFER: |....................|
+ // USED: |....############....|
+ // rOfs rLen
+
+ uint32_t rOfs = _out_pos;
+ uint32_t rLen = (_in_pos - _out_pos);
+ memcpy(newbuf + rOfs, _buf + rOfs, rLen * sizeof(_QElem));
+ } else { // WRAPPED
+ // BUFFER: |....................|
+ // USED: |######........######|
+ // r1Len r2Len
+
+ uint32_t r1Len = _in_pos;
+ uint32_t r2Len = (oldsize - _out_pos);
+ memcpy(newbuf, _buf, r1Len * sizeof(_QElem));
+ memcpy(newbuf + _bufsize - r2Len, _buf + oldsize - r2Len,
+ r2Len * sizeof(_QElem));
+ _out_pos += _bufsize - oldsize;
+ }
+ free(_buf);
+ _buf = newbuf;
+}
+
+
+FNET_PacketQueue_NoLock::FNET_PacketQueue_NoLock(uint32_t len,
+ HP_RetCode hpRetCode)
+ : _buf(NULL),
+ _bufsize(len),
+ _bufused(0),
+ _in_pos(0),
+ _out_pos(0),
+ _hpRetCode(hpRetCode)
+{
+ _buf = static_cast<_QElem *>(malloc(sizeof(_QElem) * len));
+ assert(_buf != NULL);
+}
+
+
+FNET_PacketQueue_NoLock::~FNET_PacketQueue_NoLock()
+{
+ DiscardPackets_NoLock();
+ free(_buf);
+}
+
+
+FNET_IPacketHandler::HP_RetCode
+FNET_PacketQueue_NoLock::HandlePacket(FNET_Packet *packet,
+ FNET_Context context)
+{
+ QueuePacket_NoLock(packet, context);
+ return _hpRetCode;
+}
+
+
+void
+FNET_PacketQueue_NoLock::QueuePacket_NoLock(FNET_Packet *packet,
+ FNET_Context context)
+{
+ if (packet == NULL)
+ return;
+ EnsureFree();
+ _buf[_in_pos]._packet = packet;
+ _buf[_in_pos]._context = context;
+ if (++_in_pos == _bufsize)
+ _in_pos = 0; // wrap around.
+ _bufused++;
+}
+
+
+FNET_Packet*
+FNET_PacketQueue_NoLock::DequeuePacket_NoLock(FNET_Context *context)
+{
+ assert(context != NULL);
+ FNET_Packet *packet = NULL;
+ if (_bufused > 0) {
+ packet = _buf[_out_pos]._packet;
+ __builtin_prefetch(packet, 0);
+ *context = _buf[_out_pos]._context;
+ if (++_out_pos == _bufsize)
+ _out_pos = 0; // wrap around
+ _bufused--;
+ }
+ return packet;
+}
+
+
+uint32_t
+FNET_PacketQueue_NoLock::FlushPackets_NoLock(FNET_PacketQueue_NoLock *target)
+{
+ uint32_t cnt = _bufused;
+
+ target->EnsureFree(cnt);
+ for (; _bufused > 0; _bufused--, target->_bufused++) {
+ target->_buf[target->_in_pos]._packet = _buf[_out_pos]._packet;
+ target->_buf[target->_in_pos]._context = _buf[_out_pos]._context;
+
+ if (++target->_in_pos == target->_bufsize)
+ target->_in_pos = 0; // wrap around.
+ if (++_out_pos == _bufsize)
+ _out_pos = 0; // wrap around.
+ }
+ assert(_out_pos == _in_pos);
+
+ return cnt;
+}
+
+
+void
+FNET_PacketQueue_NoLock::DiscardPackets_NoLock()
+{
+ for (; _bufused > 0; _bufused--) {
+ _buf[_out_pos]._packet->Free(); // discard packet
+ if (++_out_pos == _bufsize)
+ _out_pos = 0; // wrap around
+ }
+ assert(_out_pos == _in_pos);
+}
+
+
+void
+FNET_PacketQueue_NoLock::Print(uint32_t indent)
+{
+ uint32_t i = _out_pos;
+ uint32_t cnt = _bufused;
+
+ printf("%*sFNET_PacketQueue_NoLock {\n", indent, "");
+ printf("%*s bufsize : %d\n", indent, "", _bufsize);
+ printf("%*s bufused : %d\n", indent, "", _bufused);
+ printf("%*s in_pos : %d\n", indent, "", _in_pos);
+ printf("%*s out_pos : %d\n", indent, "", _out_pos);
+ for (; cnt > 0; i++, cnt--) {
+ if (i == _bufsize)
+ i = 0; // wrap around
+ _buf[i]._packet->Print(indent + 2);
+ _buf[i]._context.Print(indent + 2);
+ }
+ printf("%*s}\n", indent, "");
+}
+
+
+//------------------------------------------------------------------
+
+
+FNET_PacketQueue::FNET_PacketQueue(uint32_t len,
+ HP_RetCode hpRetCode)
+ : FNET_PacketQueue_NoLock(len, hpRetCode),
+ _cond(),
+ _waitCnt(0)
+{
+}
+
+
+FNET_PacketQueue::~FNET_PacketQueue()
+{
+}
+
+
+FNET_IPacketHandler::HP_RetCode
+FNET_PacketQueue::HandlePacket(FNET_Packet *packet,
+ FNET_Context context)
+{
+ QueuePacket(packet, context);
+ return _hpRetCode;
+}
+
+
+void
+FNET_PacketQueue::QueuePacket(FNET_Packet *packet, FNET_Context context)
+{
+ assert(packet != NULL);
+ Lock();
+ EnsureFree();
+ _buf[_in_pos]._packet = packet; // insert packet ref.
+ _buf[_in_pos]._context = context;
+ if (++_in_pos == _bufsize)
+ _in_pos = 0; // wrap around.
+ _bufused++;
+ if (_waitCnt >= _bufused) // signal waiting thread(s)
+ Signal();
+ Unlock();
+}
+
+
+FNET_Packet*
+FNET_PacketQueue::DequeuePacket(FNET_Context *context)
+{
+ FNET_Packet *packet = NULL;
+ Lock();
+ _waitCnt++;
+ while (_bufused == 0)
+ Wait();
+ _waitCnt--;
+ packet = _buf[_out_pos]._packet;
+ *context = _buf[_out_pos]._context;
+ if (++_out_pos == _bufsize)
+ _out_pos = 0; // wrap around
+ _bufused--;
+ Unlock();
+ return packet;
+}
+
+
+FNET_Packet*
+FNET_PacketQueue::DequeuePacket(uint32_t maxwait, FNET_Context *context)
+{
+ FNET_Packet *packet = NULL;
+ FastOS_Time startTime;
+ int waitTime;
+
+ if (maxwait > 0)
+ startTime.SetNow();
+ Lock();
+ if (maxwait > 0) {
+ bool timeout = false;
+
+ _waitCnt++;
+ while ((_bufused == 0) && !timeout
+ && (waitTime = (int)(maxwait - startTime.MilliSecsToNow())) > 0)
+ timeout = !TimedWait(waitTime);
+ _waitCnt--;
+ }
+ if (_bufused > 0) {
+ packet = _buf[_out_pos]._packet;
+ *context = _buf[_out_pos]._context;
+ if (++_out_pos == _bufsize)
+ _out_pos = 0; // wrap around
+ _bufused--;
+ }
+ Unlock();
+ return packet;
+}
+
+
+void
+FNET_PacketQueue::Print(uint32_t indent)
+{
+ Lock();
+ uint32_t i = _out_pos;
+ uint32_t cnt = _bufused;
+
+ printf("%*sFNET_PacketQueue {\n", indent, "");
+ printf("%*s bufsize : %d\n", indent, "", _bufsize);
+ printf("%*s bufused : %d\n", indent, "", _bufused);
+ printf("%*s in_pos : %d\n", indent, "", _in_pos);
+ printf("%*s out_pos : %d\n", indent, "", _out_pos);
+ printf("%*s waitCnt : %d\n", indent, "", _waitCnt);
+ for (; cnt > 0; i++, cnt--) {
+ if (i == _bufsize)
+ i = 0; // wrap around
+ _buf[i]._packet->Print(indent + 2);
+ _buf[i]._context.Print(indent + 2);
+ }
+ printf("%*s}\n", indent, "");
+ Unlock();
+}
diff --git a/fnet/src/vespa/fnet/packetqueue.h b/fnet/src/vespa/fnet/packetqueue.h
new file mode 100644
index 00000000000..baf00c24cb9
--- /dev/null
+++ b/fnet/src/vespa/fnet/packetqueue.h
@@ -0,0 +1,284 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class implements a queue of packets. Being in a queue does not
+ * affect the packet's internal data. This is the superclass of the
+ * @ref FNET_PacketQueue. As seen by its name, this class has no
+ * locking. All functionallity offered by this class is also available
+ * in the subclass. However, this class may be a good lightweight
+ * alternative to the heavier subclass in single threaded applications
+ * or where the surrounding code handles thread-safety.
+ **/
+class FNET_PacketQueue_NoLock : public FNET_IPacketHandler
+{
+private:
+ FNET_PacketQueue_NoLock(const FNET_PacketQueue_NoLock &);
+ FNET_PacketQueue_NoLock &operator=(const FNET_PacketQueue_NoLock &);
+
+
+protected:
+#ifndef IAM_DOXYGEN
+ struct _QElem
+ {
+ FNET_Packet *_packet;
+ FNET_Context _context;
+ protected:
+ _QElem() : _packet(NULL), _context() {}
+ private:
+ _QElem(const _QElem &);
+ _QElem &operator=(const _QElem &);
+ };
+#endif // DOXYGEN
+
+ _QElem *_buf;
+ uint32_t _bufsize;
+ uint32_t _bufused;
+ uint32_t _in_pos;
+ uint32_t _out_pos;
+ HP_RetCode _hpRetCode; // HandlePacket return value
+
+
+ /**
+ * Ensure that we have enough free space on the queue. Calling this
+ * method will expand the queue by calling the ExpandBuf method if
+ * there is insufficient free space.
+ *
+ * @param needentries the number of free packet entries needed.
+ **/
+ void EnsureFree(uint32_t needentries = 1)
+ {
+ if (_bufsize < _bufused + needentries)
+ ExpandBuf(needentries);
+ }
+
+
+ /**
+ * Expand the buffer capacity of this queue.
+ *
+ * @param needentries the number of free packet entries needed.
+ **/
+ void ExpandBuf(uint32_t needentries);
+
+
+public:
+
+ /**
+ * Construct a packet queue.
+ *
+ * @param len initial number of free packet entries. Default is 64.
+ * @param hpRetCode the value that should be returned when used
+ * as a packet handler. Default is FNET_KEEP_CHANNEL.
+ **/
+ FNET_PacketQueue_NoLock(uint32_t len = 64,
+ HP_RetCode hpRetCode = FNET_KEEP_CHANNEL);
+ virtual ~FNET_PacketQueue_NoLock();
+
+
+ /**
+ * Handle incoming packet by putting it on the queue. This method
+ * uses the hpRetCode value given to the constructor to decide what
+ * to do with the channel delivering the packet.
+ *
+ * @return channel command: keep open, close or free.
+ * @param packet the packet to handle.
+ * @param context the packet context.
+ **/
+ virtual HP_RetCode HandlePacket(FNET_Packet *packet,
+ FNET_Context context);
+
+
+ /**
+ * Queue a packet. NOTE: packet handover (caller TO invoked object).
+ *
+ * @param packet the packet you want to queue.
+ * @param context the context for the packet.
+ **/
+ void QueuePacket_NoLock(FNET_Packet *packet, FNET_Context context);
+
+
+ /**
+ * Check if the queue is empty.
+ *
+ * @return true if empty, false otherwise.
+ **/
+ bool IsEmpty_NoLock() { return _bufused == 0; }
+
+
+ /**
+ * Obtain the number of packets on the queue.
+ *
+ * @return number of packets on the queue.
+ **/
+ uint32_t GetPacketCnt_NoLock() { return _bufused; }
+
+
+ /**
+ * Remove the first packet from the queue and return it. If the
+ * queue was empty, NULL is returned. NOTE: packet handover (invoked
+ * object TO caller).
+ *
+ * @return first packet in queue or NULL.
+ * @param context where to store the packet context.
+ **/
+ FNET_Packet *DequeuePacket_NoLock(FNET_Context *context);
+
+
+ /**
+ * Move all packets currently in this packet queue into the queue
+ * given as parameter. NOTE: caller should have exclusive access to
+ * the packet queue given as parameter.
+ *
+ * @return number of packets flushed.
+ * @param target where to flush the packets.
+ **/
+ uint32_t FlushPackets_NoLock(FNET_PacketQueue_NoLock *target);
+
+
+ /**
+ * This method is called by the destructor to discard (invoke Free
+ * on) all packets in this packet queue. This method is also called
+ * by the FNET_Connection::Close method in order to get rid of the
+ * packets in the output queue as soon as possible.
+ **/
+ void DiscardPackets_NoLock();
+
+
+ /**
+ * Print the contents of this packet queue to stdout. Useful for
+ * debugging purposes.
+ **/
+ void Print(uint32_t indent = 0);
+};
+
+
+//------------------------------------------------------------------
+
+
+/**
+ * This class implements a queue of packets. Being in a queue does not
+ * affect the packet's internal data. This is an extension of the @ref
+ * FNET_PacketQueue_NoLock class that also supports thread-safe
+ * operations. The indirectly inherited packethandler callback method
+ * and the print method are overridden to support thread-safe
+ * behavior.
+ **/
+class FNET_PacketQueue : public FNET_PacketQueue_NoLock
+{
+private:
+ FNET_PacketQueue(const FNET_PacketQueue &);
+ FNET_PacketQueue &operator=(const FNET_PacketQueue &);
+
+
+protected:
+ FNET_Cond _cond;
+ uint32_t _waitCnt;
+
+
+public:
+
+ /**
+ * Construct a packet queue.
+ *
+ * @param len initial number of free packet entries. Default is 64.
+ * @param hpRetCode the value that should be returned when used
+ * as a packet handler. Default is FNET_KEEP_CHANNEL.
+ **/
+ FNET_PacketQueue(uint32_t len = 64,
+ HP_RetCode hpRetCode = FNET_KEEP_CHANNEL);
+ virtual ~FNET_PacketQueue();
+
+
+ /**
+ * Lock this queue to gain exclusive access.
+ **/
+ void Lock() { _cond.Lock(); }
+
+
+ /**
+ * Unlock this queue to yield exclusive access.
+ **/
+ void Unlock() { _cond.Unlock(); }
+
+
+ /**
+ * Wait for a signal on this queue.
+ **/
+ void Wait() { _cond.Wait(); }
+
+
+ /**
+ * Wait for a signal on this queue, but time out after ms
+ * milliseconds.
+ *
+ * @return true(got signal)/false(timeout).
+ * @param ms number of milliseconds to wait before timing out.
+ **/
+ bool TimedWait(int ms) { return _cond.TimedWait(ms); }
+
+
+ /**
+ * Signal one thread waiting on this queue.
+ **/
+ void Signal() { _cond.Signal(); }
+
+
+ /**
+ * Handle incoming packet by putting it on the queue. This method
+ * uses the hpRetCode value given to the constructor to decide what
+ * to do with the channel delivering the packet.
+ *
+ * @return channel command: keep open, close or free.
+ * @param packet the packet to handle.
+ * @param context the packet context.
+ **/
+ virtual HP_RetCode HandlePacket(FNET_Packet *packet,
+ FNET_Context context);
+
+
+ /**
+ * Insert a packet into this packet queue. If the queue is too small
+ * it will be extended automatically. NOTE: packet handover (caller
+ * TO invoked object).
+ *
+ * @param packet packet you want to queue.
+ * @param context the context for the packet.
+ **/
+ void QueuePacket(FNET_Packet *packet, FNET_Context context);
+
+
+ /**
+ * Obtain the first packet in this packet queue. If the queue is
+ * currently empty, the calling thread will wait until a packet is
+ * available on the queue. NOTE: packet handover (invoked object TO
+ * caller)
+ *
+ * @return a packet obtained from this queue
+ * @param context where to store the packet context.
+ **/
+ FNET_Packet *DequeuePacket(FNET_Context *context);
+
+
+ /**
+ * Obtain the first packet in this packet queue. If the queue is
+ * currently empty, the calling thread will wait until a packet is
+ * available on the queue, but for no more than 'maxwait'
+ * milliseconds. NOTE: packet handover (invoked object TO caller)
+ *
+ * @return a packet obtained from the queue or NULL.
+ * @param maxwait maximum number of milliseconds before this
+ * method call returns.
+ * @param context where to store packet context.
+ **/
+ FNET_Packet *DequeuePacket(uint32_t maxwait,
+ FNET_Context *context);
+
+
+ /**
+ * Print the contents of this packet queue to stdout. Useful for
+ * debugging purposes.
+ **/
+ void Print(uint32_t indent = 0);
+};
+
diff --git a/fnet/src/vespa/fnet/scheduler.cpp b/fnet/src/vespa/fnet/scheduler.cpp
new file mode 100644
index 00000000000..63d107bc334
--- /dev/null
+++ b/fnet/src/vespa/fnet/scheduler.cpp
@@ -0,0 +1,175 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet.scheduler");
+#include <vespa/fnet/fnet.h>
+#include <string>
+#include <sstream>
+
+FNET_Scheduler::FNET_Scheduler(FastOS_Time *sampler,
+ FastOS_Time *now)
+ : _cond(),
+ _next(),
+ _now(),
+ _sampler(sampler),
+ _currIter(0),
+ _currSlot(0),
+ _currPt(NULL),
+ _tailPt(NULL),
+ _performing(NULL),
+ _waitTask(false)
+{
+ for (int i = 0; i < NUM_SLOTS; i++)
+ _slots[i] = NULL;
+ _slots[NUM_SLOTS] = NULL;
+
+ if (now != NULL) {
+ _next = *now;
+ } else {
+ _next.SetNow();
+ }
+ _next.AddMilliSecs(SLOT_TICK);
+}
+
+
+FNET_Scheduler::~FNET_Scheduler()
+{
+ if (LOG_WOULD_LOG(debug)) {
+ bool empty = true;
+ std::stringstream dump;
+ Lock();
+ dump << "FNET_Scheduler {" << std::endl;
+ dump << " [slot=" << _currSlot << "][iter=" << _currIter << "]" << std::endl;
+ for (int i = 0; i <= NUM_SLOTS; i++) {
+ FNET_Task *pt = _slots[i];
+ if (pt != NULL) {
+ empty = false;
+ FNET_Task *end = pt;
+ do {
+ dump << " FNET_Task { slot=" << pt->_task_slot;
+ dump << ", iter=" << pt->_task_iter << " }" << std::endl;
+ pt = pt->_task_next;
+ } while (pt != end);
+ }
+ }
+ dump << "}" << std::endl;
+ Unlock();
+ if (!empty) {
+ LOG(debug, "~FNET_Scheduler(): tasks still pending when deleted"
+ "\n%s", dump.str().c_str());
+ }
+ }
+}
+
+
+void
+FNET_Scheduler::Schedule(FNET_Task *task, double seconds)
+{
+ uint32_t ticks = 1 + (uint32_t) (seconds * (1000 / SLOT_TICK) + 0.5);
+
+ Lock();
+ if (!task->_killed) {
+ if (IsActive(task))
+ LinkOut(task);
+ task->_task_slot = (ticks + _currSlot) & SLOTS_MASK;
+ task->_task_iter = _currIter + ((ticks + _currSlot) >> SLOTS_SHIFT);
+ LinkIn(task);
+ }
+ Unlock();
+}
+
+
+void
+FNET_Scheduler::ScheduleNow(FNET_Task *task)
+{
+ Lock();
+ if (!task->_killed) {
+ if (IsActive(task))
+ LinkOut(task);
+ task->_task_slot = NUM_SLOTS;
+ task->_task_iter = 0;
+ LinkIn(task);
+ }
+ Unlock();
+}
+
+
+void
+FNET_Scheduler::Unschedule(FNET_Task *task)
+{
+ Lock();
+ WaitTask(task);
+ if (IsActive(task))
+ LinkOut(task);
+ Unlock();
+}
+
+
+void
+FNET_Scheduler::Kill(FNET_Task *task)
+{
+ Lock();
+ WaitTask(task);
+ if (IsActive(task))
+ LinkOut(task);
+ task->_killed = true;
+ Unlock();
+}
+
+
+void
+FNET_Scheduler::Print(FILE *dst)
+{
+ Lock();
+ fprintf(dst, "FNET_Scheduler {\n");
+ fprintf(dst, " [slot=%d][iter=%d]\n", _currSlot, _currIter);
+ for (int i = 0; i <= NUM_SLOTS; i++) {
+ FNET_Task *pt = _slots[i];
+ if (pt != NULL) {
+ FNET_Task *end = pt;
+ do {
+ fprintf(dst, " FNET_Task { slot=%d, iter=%d }\n",
+ pt->_task_slot, pt->_task_iter);
+ pt = pt->_task_next;
+ } while (pt != end);
+ }
+ }
+ fprintf(dst, "}\n");
+ Unlock();
+}
+
+
+void
+FNET_Scheduler::CheckTasks()
+{
+ if (_sampler != NULL) {
+ _now = *_sampler;
+ } else {
+ _now.SetNow();
+ }
+
+ // assume timely value propagation
+
+ if (_slots[NUM_SLOTS] == NULL && _now < _next)
+ return;
+
+ Lock();
+
+ // perform urgent tasks
+
+ PerformTasks(NUM_SLOTS, 0);
+
+ // handle bucket timeout(s)
+
+ for (int i = 0; _now >= _next; ++i, _next.AddMilliSecs(SLOT_TICK)) {
+ if (i < 25) {
+ if (++_currSlot >= NUM_SLOTS) {
+ _currSlot = 0;
+ _currIter++;
+ }
+ PerformTasks(_currSlot, _currIter);
+ }
+ }
+ Unlock();
+}
diff --git a/fnet/src/vespa/fnet/scheduler.h b/fnet/src/vespa/fnet/scheduler.h
new file mode 100644
index 00000000000..0e60da8b9cf
--- /dev/null
+++ b/fnet/src/vespa/fnet/scheduler.h
@@ -0,0 +1,248 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * An object of this class handles scheduling of @ref FNET_Task
+ * objects. A task may be scheduled to be performed in a given number
+ * of seconds. A scheduled task may also be unscheduled to cancel the
+ * performing of that task. A scheduler object does not have its own
+ * thread, but depends on being invoked regularely to perform pending
+ * tasks.
+ **/
+class FNET_Scheduler
+{
+public:
+
+ enum scheduler_constants {
+ SLOT_TICK = 10,
+ NUM_SLOTS = 4096,
+ SLOTS_MASK = 4095,
+ SLOTS_SHIFT = 12
+ };
+
+private:
+ FNET_Cond _cond;
+ FNET_Task *_slots[NUM_SLOTS + 1];
+ FastOS_Time _next;
+ FastOS_Time _now;
+ FastOS_Time *_sampler;
+ uint32_t _currIter;
+ uint32_t _currSlot;
+ FNET_Task *_currPt;
+ FNET_Task *_tailPt;
+ FNET_Task *_performing;
+ bool _waitTask;
+
+ FNET_Scheduler(const FNET_Scheduler &);
+ FNET_Scheduler &operator=(const FNET_Scheduler &);
+
+ void Lock() { _cond.Lock(); }
+ void Unlock() { _cond.Unlock(); }
+ void Wait() { _cond.Wait(); }
+ void Broadcast() { _cond.Broadcast(); }
+
+
+ FNET_Task *GetTask()
+ {
+ return _currPt;
+ }
+
+
+ void FirstTask(uint32_t slot)
+ {
+ _currPt = _slots[slot];
+ _tailPt = (_currPt != NULL) ?
+ _currPt->_task_prev : NULL;
+ }
+
+
+ void NextTask()
+ {
+ _currPt = (_currPt != _tailPt) ?
+ _currPt->_task_next : NULL;
+ }
+
+
+ void AdjustCurrPt()
+ {
+ _currPt = (_currPt != _tailPt) ?
+ _currPt->_task_next : NULL;
+ }
+
+
+ void AdjustTailPt()
+ {
+ _tailPt = _tailPt->_task_prev;
+ }
+
+
+ void LinkIn(FNET_Task *task)
+ {
+ FNET_Task **head = &(_slots[task->_task_slot]);
+
+ if ((*head) == NULL) {
+ (*head) = task;
+ task->_task_next = task;
+ task->_task_prev = task;
+ } else {
+ task->_task_next = (*head);
+ task->_task_prev = (*head)->_task_prev;
+ (*head)->_task_prev->_task_next = task;
+ (*head)->_task_prev = task;
+ }
+ }
+
+
+ void LinkOut(FNET_Task *task)
+ {
+ FNET_Task **head = &(_slots[task->_task_slot]);
+
+ if (task == _currPt)
+ AdjustCurrPt();
+ else if (task == _tailPt)
+ AdjustTailPt();
+
+ if (task->_task_next == task) {
+ (*head) = NULL;
+ } else {
+ task->_task_prev->_task_next = task->_task_next;
+ task->_task_next->_task_prev = task->_task_prev;
+ if ((*head) == task)
+ (*head) = task->_task_next;
+ }
+ task->_task_next = NULL;
+ task->_task_prev = NULL;
+ }
+
+
+ static bool IsActive(FNET_Task *task)
+ { return task->_task_next != NULL; }
+
+
+ bool IsPerforming(FNET_Task *task)
+ { return task == _performing; }
+
+
+ void BeforeTask(FNET_Task *task)
+ {
+ _performing = task;
+ Unlock();
+ }
+
+
+ void AfterTask()
+ {
+ Lock();
+ _performing = NULL;
+ if (_waitTask) {
+ _waitTask = false;
+ Broadcast();
+ }
+ }
+
+
+ void WaitTask(FNET_Task *task)
+ {
+ while (IsPerforming(task)) {
+ _waitTask = true;
+ Wait();
+ }
+ }
+
+
+ void PerformTasks(uint32_t slot, uint32_t iter)
+ {
+ FirstTask(slot);
+ for (FNET_Task *task; (task = GetTask()) != NULL; ) {
+ NextTask();
+
+ if (task->_task_iter == iter) {
+ LinkOut(task);
+ BeforeTask(task);
+ task->PerformTask(); // PERFORM TASK
+ AfterTask();
+ }
+ }
+ }
+
+public:
+
+ /**
+ * Construct a scheduler.
+ *
+ * @param sampler if given, this object will be used to obtain the
+ * time when the @ref CheckTasks method is invoked. If a
+ * sampler is not given, time sampling will be
+ * handled internally.
+ * @param now if given, indicates the current time. This value is
+ * used by the constructor to init internal variables.
+ **/
+ FNET_Scheduler(FastOS_Time *sampler = NULL,
+ FastOS_Time *now = NULL);
+ virtual ~FNET_Scheduler();
+
+
+ /**
+ * Schedule a task to be performed in the given amount of
+ * seconds.
+ *
+ * @param task the task to be scheduled.
+ * @param seconds the number of seconds until the task
+ * should be performed.
+ **/
+ void Schedule(FNET_Task *task, double seconds);
+
+
+ /**
+ * Schedule a task to be performed as soon as possible.
+ *
+ * @param task the task to be scheduled.
+ **/
+ void ScheduleNow(FNET_Task *task);
+
+
+ /**
+ * Unschedule the given task. If the task is currently being
+ * performed, this method will block until the task is
+ * completed. This means that a task trying to unschedule itself
+ * will result in a deadlock.
+ *
+ * @param task the task to unschedule.
+ **/
+ void Unschedule(FNET_Task *task);
+
+
+ /**
+ * This method does the same as the @ref Unschedule method, but also
+ * makes sure that the task may not be scheduled in the future.
+ **/
+ void Kill(FNET_Task *task);
+
+
+ /**
+ * Print all currently scheduled tasks to the given file stream
+ * (default is stdout). This method may be used for debugging.
+ *
+ * @param dst where to print the contents of this scheduler
+ **/
+ void Print(FILE *dst = stdout);
+
+
+ /**
+ * Obtain a pointer to the current time sampler used by this
+ * scheduler. The returned object may only be used in the thread
+ * servicing this scheduler; this includes all tasks performed by
+ * this scheduler.
+ *
+ * @return pointer to current time sampler.
+ **/
+ FastOS_Time *GetTimeSampler() { return _sampler; }
+
+
+ /**
+ * Perform pending tasks. This method should be invoked regularly.
+ **/
+ void CheckTasks();
+};
+
diff --git a/fnet/src/vespa/fnet/signalshutdown.cpp b/fnet/src/vespa/fnet/signalshutdown.cpp
new file mode 100644
index 00000000000..fc9a504bdea
--- /dev/null
+++ b/fnet/src/vespa/fnet/signalshutdown.cpp
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/signalhandler.h>
+
+void
+FNET_SignalShutDown::PerformTask()
+{
+ typedef vespalib::SignalHandler SIG;
+ if (SIG::INT.check() || SIG::TERM.check()) {
+ fprintf(stderr, "got signal, shutting down...\n");
+ _transport.ShutDown(false);
+ } else {
+ Schedule(0.1);
+ }
+}
+
+void
+FNET_SignalShutDown::hookSignals()
+{
+ typedef vespalib::SignalHandler SIG;
+ SIG::INT.hook();
+ SIG::TERM.hook();
+}
diff --git a/fnet/src/vespa/fnet/signalshutdown.h b/fnet/src/vespa/fnet/signalshutdown.h
new file mode 100644
index 00000000000..755f7a6080c
--- /dev/null
+++ b/fnet/src/vespa/fnet/signalshutdown.h
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * Utility class that will shut down a transport when the process gets
+ * either INT or TERM.
+ **/
+class FNET_SignalShutDown : FNET_Task
+{
+private:
+ FNET_Transport &_transport;
+
+public:
+ FNET_SignalShutDown(FNET_Transport &t) : FNET_Task(t.GetScheduler()), _transport(t) {
+ ScheduleNow();
+ }
+ virtual void PerformTask();
+
+ /**
+ * Set up signal handling to hook appropriate signals.
+ **/
+ static void hookSignals();
+};
+
diff --git a/fnet/src/vespa/fnet/simplepacketstreamer.cpp b/fnet/src/vespa/fnet/simplepacketstreamer.cpp
new file mode 100644
index 00000000000..46b782c16a0
--- /dev/null
+++ b/fnet/src/vespa/fnet/simplepacketstreamer.cpp
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+FNET_SimplePacketStreamer::FNET_SimplePacketStreamer(FNET_IPacketFactory *factory)
+ : _factory(factory)
+{
+}
+
+
+FNET_SimplePacketStreamer::~FNET_SimplePacketStreamer()
+{
+}
+
+
+bool
+FNET_SimplePacketStreamer::GetPacketInfo(FNET_DataBuffer *src, uint32_t *plen,
+ uint32_t *pcode, uint32_t *chid,
+ bool *broken)
+{
+ (void) broken;
+
+ if (src->GetDataLen() < 3 * sizeof(uint32_t))
+ return false;
+
+ *plen = src->ReadInt32() - 2 * sizeof(uint32_t);
+ *pcode = src->ReadInt32();
+ *chid = src->ReadInt32();
+ return true;
+}
+
+
+FNET_Packet*
+FNET_SimplePacketStreamer::Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t pcode, FNET_Context context)
+{
+ FNET_Packet *packet;
+
+ packet = _factory->CreatePacket(pcode, context);
+ if (packet != NULL) {
+ if (!packet->Decode(src, plen)) {
+ packet->Free();
+ packet = NULL;
+ }
+ } else {
+ src->DataToDead(plen);
+ }
+ src->AssertValid();
+ return packet;
+}
+
+
+void
+FNET_SimplePacketStreamer::Encode(FNET_Packet *packet, uint32_t chid,
+ FNET_DataBuffer *dst)
+{
+ uint32_t len = packet->GetLength();
+ uint32_t pcode = packet->GetPCODE();
+ dst->EnsureFree(len + 3 * sizeof(uint32_t));
+ dst->WriteInt32Fast(len + 2 * sizeof(uint32_t));
+ dst->WriteInt32Fast(pcode);
+ dst->WriteInt32Fast(chid);
+ packet->Encode(dst);
+ dst->AssertValid();
+}
diff --git a/fnet/src/vespa/fnet/simplepacketstreamer.h b/fnet/src/vespa/fnet/simplepacketstreamer.h
new file mode 100644
index 00000000000..30719559fda
--- /dev/null
+++ b/fnet/src/vespa/fnet/simplepacketstreamer.h
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This is a convenience class. Large applications may want to
+ * implement the functionality offered by this class themselves to
+ * obtain better control. A simple but useful implementation of the
+ * packet streamer interface. Packets are transmitted with a packet
+ * header containing packet length, packet code and channel id. This
+ * gives us a total packet header length of 12 bytes. In order to use
+ * this packet streamer you must supply an object implementing the
+ * packet factory interface to the constructor. The EnsureFree method
+ * on the target databuffer is used to ensure that there is enough
+ * free space for packets to be encoded. This means that when the
+ * packet Encode method is invoked the databuffer has at least as much
+ * free space as reported by the packet Length method. After each
+ * packet encode/decode the AssertValid databuffer method is run to
+ * check for illegal reads/writes.
+ **/
+class FNET_SimplePacketStreamer : public FNET_IPacketStreamer
+{
+private:
+ FNET_IPacketFactory *_factory;
+
+ FNET_SimplePacketStreamer(const FNET_SimplePacketStreamer &);
+ FNET_SimplePacketStreamer &operator=(const FNET_SimplePacketStreamer &);
+
+public:
+ FNET_SimplePacketStreamer(FNET_IPacketFactory *factory);
+ virtual ~FNET_SimplePacketStreamer();
+
+ bool GetPacketInfo(FNET_DataBuffer *src, uint32_t *plen,
+ uint32_t *pcode, uint32_t *chid,
+ bool *broken);
+ FNET_Packet *Decode(FNET_DataBuffer *src, uint32_t plen,
+ uint32_t pcode, FNET_Context context);
+ void Encode(FNET_Packet *packet, uint32_t chid, FNET_DataBuffer *dst);
+};
+
diff --git a/fnet/src/vespa/fnet/spiral/.gitignore b/fnet/src/vespa/fnet/spiral/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/vespa/fnet/spiral/.gitignore
diff --git a/fnet/src/vespa/fnet/stats.cpp b/fnet/src/vespa/fnet/stats.cpp
new file mode 100644
index 00000000000..3c128c96c7c
--- /dev/null
+++ b/fnet/src/vespa/fnet/stats.cpp
@@ -0,0 +1,98 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+
+FNET_StatCounters::FNET_StatCounters()
+ : _eventLoopCnt(0),
+ _eventCnt(0),
+ _ioEventCnt(0),
+ _packetReadCnt(0),
+ _packetWriteCnt(0),
+ _dataReadCnt(0),
+ _dataWriteCnt(0)
+{
+}
+
+
+FNET_StatCounters::~FNET_StatCounters()
+{
+}
+
+
+void
+FNET_StatCounters::Clear()
+{
+ _eventLoopCnt = 0;
+ _eventCnt = 0;
+ _ioEventCnt = 0;
+ _packetReadCnt = 0;
+ _packetWriteCnt = 0;
+ _dataReadCnt = 0;
+ _dataWriteCnt = 0;
+}
+
+//-----------------------------------------------
+
+FNET_Stats::FNET_Stats()
+ : _eventLoopRate(0),
+ _eventRate(0),
+ _ioEventRate(0),
+ _packetReadRate(0),
+ _packetWriteRate(0),
+ _dataReadRate(0),
+ _dataWriteRate(0)
+{
+}
+
+
+FNET_Stats::~FNET_Stats()
+{
+}
+
+
+void
+FNET_Stats::Update(FNET_StatCounters *count, double secs)
+{
+ _eventLoopRate = (float)(FNET_STATS_OLD_FACTOR * _eventLoopRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_eventLoopCnt / secs)));
+ _eventRate = (float)(FNET_STATS_OLD_FACTOR * _eventRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_eventCnt / secs)));
+ _ioEventRate = (float)(FNET_STATS_OLD_FACTOR * _ioEventRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_ioEventCnt / secs)));
+
+ _packetReadRate = (float)(FNET_STATS_OLD_FACTOR * _packetReadRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_packetReadCnt / secs)));
+ _packetWriteRate = (float)(FNET_STATS_OLD_FACTOR * _packetWriteRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_packetWriteCnt / secs)));
+
+ _dataReadRate = (float)(FNET_STATS_OLD_FACTOR * _dataReadRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_dataReadCnt / (1000.0 * secs))));
+ _dataWriteRate = (float)(FNET_STATS_OLD_FACTOR * _dataWriteRate
+ + (FNET_STATS_NEW_FACTOR
+ * ((double)count->_dataWriteCnt / (1000.0 * secs))));
+}
+
+
+void
+FNET_Stats::Log()
+{
+ LOG(info, "events[/s][loop/int/io][%.1f/%.1f/%.1f] "
+ "packets[/s][r/w][%.1f/%.1f] "
+ "data[kB/s][r/w][%.2f/%.2f]",
+ _eventLoopRate,
+ _eventRate,
+ _ioEventRate,
+ _packetReadRate,
+ _packetWriteRate,
+ _dataReadRate,
+ _dataWriteRate);
+}
diff --git a/fnet/src/vespa/fnet/stats.h b/fnet/src/vespa/fnet/stats.h
new file mode 100644
index 00000000000..f7ddf37c1b2
--- /dev/null
+++ b/fnet/src/vespa/fnet/stats.h
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class is used internally by @ref FNET_Transport objects to
+ * aggregate FNET statistics. The actual statistics are located in the
+ * @ref FNET_Stats class.
+ **/
+class FNET_StatCounters
+{
+public:
+ uint32_t _eventLoopCnt; // # event loop iterations
+ uint32_t _eventCnt; // # internal events
+ uint32_t _ioEventCnt; // # IO events
+ uint32_t _packetReadCnt; // # packets read
+ uint32_t _packetWriteCnt; // # packets written
+ uint32_t _dataReadCnt; // # bytes read
+ uint32_t _dataWriteCnt; // # bytes written
+
+ FNET_StatCounters();
+ ~FNET_StatCounters();
+
+ void Clear();
+ void CountEventLoop(uint32_t cnt) { _eventLoopCnt += cnt; }
+ void CountEvent(uint32_t cnt) { _eventCnt += cnt; }
+ void CountIOEvent(uint32_t cnt) { _ioEventCnt += cnt; }
+ void CountPacketRead(uint32_t cnt) { _packetReadCnt += cnt; }
+ void CountPacketWrite(uint32_t cnt) { _packetWriteCnt += cnt; }
+ void CountDataRead(uint32_t bytes) { _dataReadCnt += bytes; }
+ void CountDataWrite(uint32_t bytes) { _dataWriteCnt += bytes; }
+};
+
+//-----------------------------------------------
+
+#define FNET_STATS_OLD_FACTOR 0.5
+#define FNET_STATS_NEW_FACTOR 0.5
+
+/**
+ * This class contains various FNET statistics. The statistics for a
+ * @ref FNET_Transport object may be obtained by invoking the GetStats
+ * method on it.
+ **/
+class FNET_Stats
+{
+public:
+ /**
+ * Event loop iterations per second.
+ **/
+ float _eventLoopRate; // loop iterations/s
+
+ /**
+ * Internal events handled per second.
+ **/
+ float _eventRate; // internal-events/s
+
+ /**
+ * IO events handled per second.
+ **/
+ float _ioEventRate; // IO-events/s
+
+ /**
+ * Packets read per second.
+ **/
+ float _packetReadRate; // packets/s
+
+ /**
+ * Packets written per second.
+ **/
+ float _packetWriteRate; // packets/s
+
+ /**
+ * Data read per second (in kB).
+ **/
+ float _dataReadRate; // kB/s
+
+ /**
+ * Data written per second (in kB).
+ **/
+ float _dataWriteRate; // kB/s
+
+ FNET_Stats();
+ ~FNET_Stats();
+
+ /**
+ * Update statistics. The new statistics are calculated based on
+ * both the current values and the input count structure indicating
+ * what has happened since the last statistics update.
+ *
+ * @param count what has happened since last statistics update.
+ * @param secs number of seconds since last statistics update.
+ **/
+ void Update(FNET_StatCounters *count, double secs);
+
+ /**
+ * Invoking this method will generate a log message of type
+ * FNET_INFO showing the values held by this object.
+ **/
+ void Log();
+};
+
diff --git a/fnet/src/vespa/fnet/task.cpp b/fnet/src/vespa/fnet/task.cpp
new file mode 100644
index 00000000000..99a1f668189
--- /dev/null
+++ b/fnet/src/vespa/fnet/task.cpp
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+
+FNET_Task::FNET_Task(FNET_Scheduler *scheduler)
+ : _task_scheduler(scheduler),
+ _task_slot(0),
+ _task_iter(0),
+ _task_next(NULL),
+ _task_prev(NULL),
+ _killed(false)
+{
+}
+
+
+FNET_Task::~FNET_Task()
+{
+}
+
+
+void
+FNET_Task::Schedule(double seconds)
+{
+ _task_scheduler->Schedule(this, seconds);
+}
+
+
+void
+FNET_Task::ScheduleNow()
+{
+ _task_scheduler->ScheduleNow(this);
+}
+
+
+void
+FNET_Task::Unschedule()
+{
+ _task_scheduler->Unschedule(this);
+}
+
+
+void
+FNET_Task::Kill()
+{
+ _task_scheduler->Kill(this);
+}
+
+
+void
+FNET_Task::PerformTask()
+{
+}
diff --git a/fnet/src/vespa/fnet/task.h b/fnet/src/vespa/fnet/task.h
new file mode 100644
index 00000000000..b482a936e55
--- /dev/null
+++ b/fnet/src/vespa/fnet/task.h
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+class FNET_Scheduler;
+
+/**
+ * This class represent a task that may be scheduled to be performed
+ * by an instance of the @ref FNET_Scheduler class.
+ **/
+class FNET_Task
+{
+ friend class FNET_Scheduler;
+
+private:
+ FNET_Scheduler *_task_scheduler;
+ uint32_t _task_slot;
+ uint32_t _task_iter;
+ FNET_Task *_task_next;
+ FNET_Task *_task_prev;
+ bool _killed;
+
+ FNET_Task(const FNET_Task &);
+ FNET_Task &operator=(const FNET_Task &);
+
+public:
+
+ /**
+ * Construct a task that may be scheduled by the given scheduler.
+ *
+ * @param scheduler the scheduler that will be used to schedule this
+ * task.
+ **/
+ FNET_Task(FNET_Scheduler *scheduler);
+ virtual ~FNET_Task();
+
+ /**
+ * Schedule this task to be performed in the given amount of
+ * seconds.
+ *
+ * @param seconds the number of seconds until this task
+ * should be performed.
+ **/
+ void Schedule(double seconds);
+
+
+ /**
+ * Schedule this task to be performed as soon as possible.
+ **/
+ void ScheduleNow();
+
+
+ /**
+ * Unschedule this task. If the scheduler is currently performing
+ * this task, this method will block until the task is
+ * completed.
+ **/
+ void Unschedule();
+
+
+ /**
+ * This method does the same as the @ref Unschedule method, but also
+ * makes sure that this task may not be scheduled in the future.
+ **/
+ void Kill();
+
+
+ /**
+ * This method will be invoked by the scheduler to perform this
+ * task. Note that since the scheduling is one-shot, it is legal for
+ * a task to re-schedule itself in this method.
+ **/
+ virtual void PerformTask();
+};
+
diff --git a/fnet/src/vespa/fnet/testkit/xsync/.gitignore b/fnet/src/vespa/fnet/testkit/xsync/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/fnet/src/vespa/fnet/testkit/xsync/.gitignore
diff --git a/fnet/src/vespa/fnet/transport.cpp b/fnet/src/vespa/fnet/transport.cpp
new file mode 100644
index 00000000000..ef72b7ede16
--- /dev/null
+++ b/fnet/src/vespa/fnet/transport.cpp
@@ -0,0 +1,163 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/xxhash/xxhash.h>
+#include <chrono>
+
+namespace {
+
+struct HashState {
+ using clock = std::chrono::high_resolution_clock;
+
+ const void *self;
+ clock::time_point now;
+ uint64_t key_hash;
+ HashState(const void *key, size_t key_len)
+ : self(this),
+ now(clock::now()),
+ key_hash(XXH64(key, key_len, 0)) {}
+};
+
+} // namespace <unnamed>
+
+FNET_Transport::FNET_Transport(size_t num_threads)
+ : _threads(),
+ _connect_thread()
+{
+ assert(num_threads >= 1);
+ for (size_t i = 0; i < num_threads; ++i) {
+ _threads.emplace_back(new FNET_TransportThread(*this));
+ }
+}
+
+FNET_TransportThread *
+FNET_Transport::select_thread(const void *key, size_t key_len) const
+{
+ HashState hash_state(key, key_len);
+ size_t hash_value = XXH64(&hash_state, sizeof(hash_state), 0);
+ size_t thread_id = (hash_value % _threads.size());
+ return _threads[thread_id].get();
+}
+
+FNET_Connector *
+FNET_Transport::Listen(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter)
+{
+ return select_thread(spec, strlen(spec))->Listen(spec, streamer, serverAdapter);
+}
+
+FNET_Connection *
+FNET_Transport::Connect(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IPacketHandler *adminHandler,
+ FNET_Context adminContext,
+ FNET_IServerAdapter *serverAdapter,
+ FNET_Context connContext)
+{
+ return select_thread(spec, strlen(spec))->Connect(spec, streamer, adminHandler, adminContext, serverAdapter, connContext);
+}
+
+uint32_t
+FNET_Transport::GetNumIOComponents()
+{
+ uint32_t result = 0;
+ for (const auto &thread: _threads) {
+ result += thread->GetNumIOComponents();
+ }
+ return result;
+}
+
+void
+FNET_Transport::SetIOCTimeOut(uint32_t ms)
+{
+ for (const auto &thread: _threads) {
+ thread->SetIOCTimeOut(ms);
+ }
+}
+
+void
+FNET_Transport::SetMaxInputBufferSize(uint32_t bytes)
+{
+ for (const auto &thread: _threads) {
+ thread->SetMaxInputBufferSize(bytes);
+ }
+}
+
+void
+FNET_Transport::SetMaxOutputBufferSize(uint32_t bytes)
+{
+ for (const auto &thread: _threads) {
+ thread->SetMaxOutputBufferSize(bytes);
+ }
+}
+
+void
+FNET_Transport::SetDirectWrite(bool directWrite)
+{
+ for (const auto &thread: _threads) {
+ thread->SetDirectWrite(directWrite);
+ }
+}
+
+void
+FNET_Transport::SetTCPNoDelay(bool noDelay)
+{
+ for (const auto &thread: _threads) {
+ thread->SetTCPNoDelay(noDelay);
+ }
+}
+
+void
+FNET_Transport::SetLogStats(bool logStats)
+{
+ for (const auto &thread: _threads) {
+ thread->SetLogStats(logStats);
+ }
+}
+
+void
+FNET_Transport::sync()
+{
+ for (const auto &thread: _threads) {
+ thread->sync();
+ }
+}
+
+FNET_Scheduler *
+FNET_Transport::GetScheduler()
+{
+ return select_thread(nullptr, 0)->GetScheduler();
+}
+
+bool
+FNET_Transport::execute(FNET_IExecutable *exe)
+{
+ return select_thread(nullptr, 0)->execute(exe);
+}
+
+void
+FNET_Transport::ShutDown(bool waitFinished)
+{
+ for (const auto &thread: _threads) {
+ thread->ShutDown(waitFinished);
+ }
+}
+
+void
+FNET_Transport::WaitFinished()
+{
+ for (const auto &thread: _threads) {
+ thread->WaitFinished();
+ }
+}
+
+bool
+FNET_Transport::Start(FastOS_ThreadPool *pool)
+{
+ bool result = true;
+ for (const auto &thread: _threads) {
+ result &= thread->Start(pool);
+ }
+ return result;
+}
diff --git a/fnet/src/vespa/fnet/transport.h b/fnet/src/vespa/fnet/transport.h
new file mode 100644
index 00000000000..fee94c9d2a5
--- /dev/null
+++ b/fnet/src/vespa/fnet/transport.h
@@ -0,0 +1,291 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "connect_thread.h"
+
+/**
+ * This class represents the transport layer and handles a collection
+ * of transport threads. Note: remember to shut down your transport
+ * layer appropriately before deleting it.
+ **/
+class FNET_Transport
+{
+private:
+ using Thread = std::unique_ptr<FNET_TransportThread>;
+ using Threads = std::vector<Thread>;
+
+ Threads _threads;
+ fnet::ConnectThread _connect_thread;
+
+public:
+ /**
+ * Construct a transport layer. To activate your newly created
+ * transport object you need to call either the Start method to
+ * spawn a new thread(s) to handle IO, or the Main method to let
+ * the current thread become the transport thread. Main may only
+ * be called for single-threaded transports.
+ **/
+ FNET_Transport(size_t num_threads = 1);
+
+ /**
+ * Calling this function gives away 1 reference to 'conn' and
+ * ensures that the 'ext_connect' function will be called on it
+ * from another thread some time in the future.
+ **/
+ void connect_later(fnet::ExtConnectable *conn) { _connect_thread.connect_later(conn); }
+
+ /**
+ * Select one of the underlying transport threads. The selection
+ * is based on hashing the given key as well as the current stack
+ * pointer.
+ *
+ * @return selected transport thread
+ **/
+ FNET_TransportThread *select_thread(const void *key, size_t key_len) const;
+
+ /**
+ * Add a network listener in an abstract way. The given 'spec'
+ * string has the following format: 'type/where'. 'type' specifies
+ * the protocol used; currently only 'tcp' is allowed, but it is
+ * included for future extensions. 'where' specifies where to listen
+ * in a way depending on the 'type' field; with tcp this field holds
+ * a port number. Example: listen for tcp/ip connections on port
+ * 8001: spec = 'tcp/8001'. If you want to enable strict binding you
+ * may supply a hostname as well, like this:
+ * 'tcp/mycomputer.mydomain:8001'.
+ *
+ * @return the connector object, or NULL if listen failed.
+ * @param spec string specifying how and where to listen.
+ * @param streamer custom packet streamer.
+ * @param serverAdapter object for custom channel creation.
+ **/
+ FNET_Connector *Listen(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter);
+
+ /**
+ * Connect to a target host in an abstract way. The given 'spec'
+ * string has the following format: 'type/where'. 'type' specifies
+ * the protocol used; currently only 'tcp' is allowed, but it is
+ * included for future extensions. 'where' specifies where to
+ * connect in a way depending on the type field; with tcp this field
+ * holds a host name (or IP address) and a port number. Example:
+ * connect to www.fast.no on port 80 (using tcp/ip): spec =
+ * 'tcp/www.fast.no:80'. The newly created connection will be
+ * serviced by this transport layer object. If the adminHandler
+ * parameter is given, an internal admin channel is created in the
+ * connection object. The admin channel will be used to deliver
+ * packets tagged with the reserved channel id (FNET_NOID) to the
+ * admin handler.
+ *
+ * @return an object representing the new connection.
+ * @param spec string specifying how and where to connect.
+ * @param streamer custom packet streamer.
+ * @param adminHandler packet handler for incoming packets on the
+ * admin channel.
+ * @param adminContext application context to be used for incoming
+ * packets on the admin channel.
+ * @param serverAdapter adapter used to support 2way channel creation.
+ * @param connContext application context for the connection.
+ **/
+ FNET_Connection *Connect(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IPacketHandler *adminHandler = NULL,
+ FNET_Context adminContext = FNET_Context(),
+ FNET_IServerAdapter *serverAdapter = NULL,
+ FNET_Context connContext = FNET_Context());
+
+ /**
+ * This method may be used to determine how many IO Components are
+ * currently controlled by this transport layer object. Note that
+ * locking is not used, since this information is volatile anyway.
+ *
+ * @return the current number of IOComponents.
+ **/
+ uint32_t GetNumIOComponents();
+
+ /**
+ * Set the I/O Component timeout. Idle I/O Components with timeout
+ * enabled (determined by calling the ShouldTimeOut method) will
+ * time out if idle for the given number of milliseconds. An I/O
+ * component reports its un-idle-ness by calling the UpdateTimeOut
+ * method in the owning transport object. Calling this method with 0
+ * as parameter will disable I/O Component timeouts. Note that newly
+ * created transport objects begin their lives with I/O Component
+ * timeouts disabled. An I/O Component timeout has the same effect
+ * as calling the Close method in the transport object with the
+ * target I/O Component as parameter.
+ *
+ * @param ms number of milliseconds before IOC idle timeout occurs.
+ **/
+ void SetIOCTimeOut(uint32_t ms);
+
+ /**
+ * Set maximum input buffer size. This value will only affect
+ * connections that use a common input buffer when decoding
+ * incoming packets. Note that this value is not an absolute
+ * max. The buffer will still grow larger than this value if
+ * needed to decode big packets. However, when the buffer becomes
+ * larger than this value, it will be shrunk back when possible.
+ *
+ * @param bytes buffer size in bytes. 0 means unlimited.
+ **/
+ void SetMaxInputBufferSize(uint32_t bytes);
+
+ /**
+ * Set maximum output buffer size. This value will only affect
+ * connections that use a common output buffer when encoding
+ * outgoing packets. Note that this value is not an absolute
+ * max. The buffer will still grow larger than this value if needed
+ * to encode big packets. However, when the buffer becomes larger
+ * than this value, it will be shrunk back when possible.
+ *
+ * @param bytes buffer size in bytes. 0 means unlimited.
+ **/
+ void SetMaxOutputBufferSize(uint32_t bytes);
+
+ /**
+ * Enable or disable the direct write optimization. This is
+ * enabled by default and favors low latency above throughput.
+ *
+ * @param directWrite enable direct write?
+ **/
+ void SetDirectWrite(bool directWrite);
+
+ /**
+ * Enable or disable use of the TCP_NODELAY flag with sockets
+ * created by this transport object.
+ *
+ * @param noDelay true if TCP_NODELAY flag should be used.
+ **/
+ void SetTCPNoDelay(bool noDelay);
+
+ /**
+ * Enable or disable logging of FNET statistics. This feature is
+ * disabled by default.
+ *
+ * @param logStats true if stats should be logged.
+ **/
+ void SetLogStats(bool logStats);
+
+ /**
+ * Synchronize with all transport threads. This method will block
+ * until all events posted before this method was invoked has been
+ * processed. If a transport thread has been shut down (or is in
+ * the progress of being shut down) this method will instead wait
+ * for the transport thread to complete, since no more commands
+ * will be performed, and waiting would be forever. Invoking this
+ * method from a transport thread is not a good idea.
+ **/
+ void sync();
+
+ /**
+ * Obtain a pointer to a transport thread scheduler.
+ *
+ * @return transport thread scheduler.
+ **/
+ FNET_Scheduler *GetScheduler();
+
+ /**
+ * Post an execution event on one of the transport threads. The
+ * return value from this method indicate whether the execution
+ * request was accepted or not. If it was accepted, the transport
+ * thread will execute the given executable at a later
+ * time. However, if it was rejected (this method returns false),
+ * the caller of this method will need to handle the fact that the
+ * executor will never be executed. Also note that it is the
+ * responsibility of the caller to ensure that all needed context
+ * for the executor is kept alive until the time of execution. It
+ * is ok to assume that execution requests will only be rejected
+ * due to transport thread shutdown. Calling sync will ensure that
+ * all previously posted execution events are handled.
+ *
+ * @return true if the execution request was accepted, false if it was rejected
+ * @param exe the executable we want to execute in any transport thread
+ **/
+ bool execute(FNET_IExecutable *exe);
+
+ /**
+ * Calling this method will shut down the transport layer in a nice
+ * way. Note that the actual task of shutting down is performed by
+ * the transport thread. This method simply posts an event on the
+ * transport thread event queue telling it to shut down.
+ *
+ * @param waitFinished if this flag is set, the method call will not
+ * return until the transport layer is shut down. NOTE: do
+ * not set this flag if you are calling this method from a
+ * callback from the transport layer, as it will create a
+ * deadlock.
+ **/
+ void ShutDown(bool waitFinished);
+
+ /**
+ * This method will make the calling thread wait until the transport
+ * layer has been shut down. NOTE: do not invoke this method if you
+ * are in a callback from the transport layer, as it will create a
+ * deadlock. See @ref ShutDown.
+ **/
+ void WaitFinished();
+
+ /**
+ * Start transport threads. Note that the return value of this
+ * method only indicates whether the spawning of new threads went
+ * ok.
+ *
+ * @return thread create status.
+ * @param pool threadpool that may be used to spawn new threads.
+ **/
+ bool Start(FastOS_ThreadPool *pool);
+
+ //-------------------------------------------------------------------------
+ // forward async IO Component operations to their owners
+ //-------------------------------------------------------------------------
+
+ static void Add(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->Add(comp, needRef);
+ }
+
+ static void EnableRead(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->EnableRead(comp, needRef);
+ }
+
+ static void DisableRead(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->DisableRead(comp, needRef);
+ }
+
+ static void EnableWrite(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->EnableWrite(comp, needRef);
+ }
+
+ static void DisableWrite(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->DisableWrite(comp, needRef);
+ }
+
+ static void Close(FNET_IOComponent *comp, bool needRef = true) {
+ comp->Owner()->Close(comp, needRef);
+ }
+
+ //-------------------------------------------------------------------------
+ // single-threaded API forwarding. num_threads must be 1. Note: Choose
+ // only one of: (a) Start, (b) Main, (c) InitEventLoop + EventLoopIteration
+ // -------------------------------------------------------------------------
+
+ FastOS_Time *GetTimeSampler() {
+ assert(_threads.size() == 1);
+ return _threads[0]->GetTimeSampler();
+ }
+
+ bool InitEventLoop() {
+ assert(_threads.size() == 1);
+ return _threads[0]->InitEventLoop();
+ }
+
+ bool EventLoopIteration() {
+ assert(_threads.size() == 1);
+ return _threads[0]->EventLoopIteration();
+ }
+
+ void Main() {
+ assert(_threads.size() == 1);
+ _threads[0]->Main();
+ }
+};
diff --git a/fnet/src/vespa/fnet/transport_thread.cpp b/fnet/src/vespa/fnet/transport_thread.cpp
new file mode 100644
index 00000000000..9efdd087321
--- /dev/null
+++ b/fnet/src/vespa/fnet/transport_thread.cpp
@@ -0,0 +1,733 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".fnet");
+#include <vespa/fnet/fnet.h>
+#include <vespa/vespalib/util/sync.h>
+
+namespace {
+
+struct Sync : public FNET_IExecutable
+{
+ vespalib::Gate gate;
+ virtual void execute() {
+ gate.countDown();
+ }
+};
+
+} // namespace<unnamed>
+
+
+char *
+SplitString(char *input, const char *sep, int &argc, char **argv, int maxargs)
+{
+ int i;
+ int sepcnt = strlen(sep);
+
+ for (argc = 0, argv[0] = input; *input != '\0'; input++) {
+ if (*input == '[' && argc == 0 && argv[argc] == input) {
+ argv[argc] = ++input; // Skip '['
+ for (; *input != ']' && *input != '\0'; ++input);
+ if (*input == ']')
+ *input++ = '\0'; // Replace ']'
+ if (*input == '\0')
+ break;
+ }
+ for (i = 0; i < sepcnt; i++) {
+ if (*input == sep[i]) {
+ *input = '\0';
+ if (*(argv[argc]) != '\0' && ++argc >= maxargs)
+ return (input + 1); // INCOMPLETE
+ argv[argc] = (input + 1);
+ break; // inner for loop
+ }
+ }
+ }
+ if (*(argv[argc]) != '\0')
+ argc++;
+ return NULL; // COMPLETE
+}
+
+#ifndef IAM_DOXYGEN
+void
+FNET_TransportThread::StatsTask::PerformTask()
+{
+ _transport->UpdateStats();
+ Schedule(5.0);
+}
+#endif
+
+void
+FNET_TransportThread::AddComponent(FNET_IOComponent *comp)
+{
+ if (comp->ShouldTimeOut()) {
+ comp->_ioc_prev = _componentsTail;
+ comp->_ioc_next = NULL;
+ if (_componentsTail == NULL) {
+ _componentsHead = comp;
+ } else {
+ _componentsTail->_ioc_next = comp;
+ }
+ _componentsTail = comp;
+ if (_timeOutHead == NULL)
+ _timeOutHead = comp;
+ _componentCnt++;
+ } else {
+ comp->_ioc_prev = NULL;
+ comp->_ioc_next = _componentsHead;
+ if (_componentsHead == NULL) {
+ _componentsTail = comp;
+ } else {
+ _componentsHead->_ioc_prev = comp;
+ }
+ _componentsHead = comp;
+ _componentCnt++;
+ }
+}
+
+
+void
+FNET_TransportThread::RemoveComponent(FNET_IOComponent *comp)
+{
+ if (comp == _componentsHead)
+ _componentsHead = comp->_ioc_next;
+ if (comp == _timeOutHead)
+ _timeOutHead = comp->_ioc_next;
+ if (comp == _componentsTail)
+ _componentsTail = comp->_ioc_prev;
+ if (comp->_ioc_prev != NULL)
+ comp->_ioc_prev->_ioc_next = comp->_ioc_next;
+ if (comp->_ioc_next != NULL)
+ comp->_ioc_next->_ioc_prev = comp->_ioc_prev;
+ _componentCnt--;
+}
+
+
+void
+FNET_TransportThread::UpdateTimeOut(FNET_IOComponent *comp)
+{
+ comp->_ioc_timestamp = _now;
+ RemoveComponent(comp);
+ AddComponent(comp);
+}
+
+
+void
+FNET_TransportThread::AddDeleteComponent(FNET_IOComponent *comp)
+{
+ assert(!comp->_flags._ioc_delete);
+ comp->_flags._ioc_added = false;
+ comp->_flags._ioc_delete = true;
+ comp->_ioc_prev = NULL;
+ comp->_ioc_next = _deleteList;
+ _deleteList = comp;
+}
+
+
+void
+FNET_TransportThread::FlushDeleteList()
+{
+ while (_deleteList != NULL) {
+ FNET_IOComponent *tmp = _deleteList;
+ _deleteList = tmp->_ioc_next;
+ assert(tmp->_flags._ioc_delete);
+ tmp->SubRef();
+ }
+}
+
+
+bool
+FNET_TransportThread::PostEvent(FNET_ControlPacket *cpacket,
+ FNET_Context context)
+{
+ bool wasEmpty;
+ Lock();
+ if (_shutdown) {
+ Unlock();
+ DiscardEvent(cpacket, context);
+ return false;
+ }
+ wasEmpty = _queue.IsEmpty_NoLock();
+ _queue.QueuePacket_NoLock(cpacket, context);
+ Unlock();
+ if (wasEmpty) {
+ _socketEvent.AsyncWakeUp();
+ }
+ return true;
+}
+
+
+void
+FNET_TransportThread::DiscardEvent(FNET_ControlPacket *cpacket,
+ FNET_Context context)
+{
+ switch (cpacket->GetCommand()) {
+ case FNET_ControlPacket::FNET_CMD_IOC_ADD:
+ context._value.IOC->Close();
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_ENABLE_READ:
+ case FNET_ControlPacket::FNET_CMD_IOC_DISABLE_READ:
+ case FNET_ControlPacket::FNET_CMD_IOC_ENABLE_WRITE:
+ case FNET_ControlPacket::FNET_CMD_IOC_DISABLE_WRITE:
+ case FNET_ControlPacket::FNET_CMD_IOC_CLOSE:
+ context._value.IOC->SubRef();
+ break;
+ }
+}
+
+
+void
+FNET_TransportThread::UpdateStats()
+{
+ _now.SetNow(); // trade some overhead for better stats
+ double ms = _now.MilliSecs() - _statTime.MilliSecs();
+ _statTime = _now;
+ for (FNET_IOComponent *comp = _componentsHead;
+ comp != NULL; comp = comp->_ioc_next)
+ {
+ comp->Lock();
+ comp->FlushDirectWriteStats();
+ comp->Unlock();
+ }
+ Lock();
+ _stats.Update(&_counters, ms / 1000.0);
+ Unlock();
+ _counters.Clear();
+
+ if (_config._logStats)
+ _stats.Log();
+}
+
+extern "C" {
+
+ static void pipehandler(int)
+ {
+ // nop
+ }
+
+ static void trapsigpipe()
+ {
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ sigaction(SIGPIPE, NULL, &act);
+ if (act.sa_handler == SIG_DFL) {
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = pipehandler;
+ sigaction(SIGPIPE, &act, NULL);
+ LOG(warning, "missing signal handler for SIGPIPE (added no-op)");
+ }
+ }
+
+} // extern "C"
+
+FNET_TransportThread::FNET_TransportThread(FNET_Transport &owner_in)
+ : _owner(owner_in),
+ _startTime(),
+ _now(),
+ _scheduler(&_now),
+ _counters(),
+ _stats(),
+ _statsTask(&_scheduler, this),
+ _statTime(),
+ _config(),
+ _componentsHead(NULL),
+ _timeOutHead(NULL),
+ _componentsTail(NULL),
+ _componentCnt(0),
+ _deleteList(NULL),
+ _socketEvent(),
+ _events(NULL),
+ _queue(),
+ _myQueue(),
+ _cond(),
+ _started(false),
+ _shutdown(false),
+ _finished(false),
+ _waitFinished(false),
+ _deleted(false)
+{
+ _now.SetNow();
+ assert(_socketEvent.GetCreateSuccess());
+ trapsigpipe();
+}
+
+
+FNET_TransportThread::~FNET_TransportThread()
+{
+ Lock();
+ _deleted = true;
+ Unlock();
+ if (_started && !_finished) {
+ LOG(error, "Transport: delete called on active object!");
+ }
+}
+
+
+FNET_Connector*
+FNET_TransportThread::Listen(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter)
+{
+ int speclen = strlen(spec);
+ char tmp[1024];
+ int argc;
+ char *argv[32];
+
+ assert(speclen < 1024);
+ memcpy(tmp, spec, speclen);
+ tmp[speclen] = '\0';
+ if (SplitString(tmp, "/", argc, argv, 32) != NULL
+ || argc != 2)
+ return NULL; // wrong number of parameters
+
+ // handle different connection types (currently only TCP/IP support)
+ if (strcasecmp(argv[0], "tcp") == 0) {
+ if (SplitString(argv[1], ":", argc, argv, 32) != NULL
+ || argc < 1 || argc > 2)
+ return NULL; // wrong number of parameters
+
+ int port = atoi(argv[argc - 1]); // last param is port
+ if (port < 0)
+ return NULL;
+ if (port == 0 && strcmp(argv[argc - 1], "0") != 0)
+ return NULL;
+ FNET_Connector *connector;
+ connector = new FNET_Connector(this, streamer, serverAdapter, spec, port,
+ 500, NULL, (argc == 2) ? argv[0] : NULL);
+ if (connector->Init()) {
+ connector->AddRef_NoLock();
+ Add(connector, /* needRef = */ false);
+ return connector;
+ } else {
+ delete connector;
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+}
+
+
+FNET_Connection*
+FNET_TransportThread::Connect(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IPacketHandler *adminHandler,
+ FNET_Context adminContext,
+ FNET_IServerAdapter *serverAdapter,
+ FNET_Context connContext)
+{
+ int speclen = strlen(spec);
+ char tmp[1024];
+ int argc;
+ char *argv[32];
+
+ assert(speclen < 1024);
+ memcpy(tmp, spec, speclen);
+ tmp[speclen] = '\0';
+ if (SplitString(tmp, "/", argc, argv, 32) != NULL
+ || argc != 2)
+ return NULL; // wrong number of parameters
+
+ // handle different connection types (currently only TCP/IP support)
+ if (strcasecmp(argv[0], "tcp") == 0) {
+ if (SplitString(argv[1], ":", argc, argv, 32) != NULL
+ || argc != 2)
+ return NULL; // wrong number of parameters
+
+ int port = atoi(argv[1]);
+ if (port <= 0)
+ return NULL;
+ FastOS_Socket *mysocket = new FastOS_Socket();
+ mysocket->SetAddress(port, argv[0]);
+ FNET_Connection *conn = new FNET_Connection(this, streamer, serverAdapter,
+ adminHandler, adminContext,
+ connContext, mysocket, spec);
+ if (conn->Init()) {
+ conn->AddRef_NoLock();
+ owner().connect_later(conn);
+ return conn;
+ } else {
+ delete conn;
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+}
+
+
+void
+FNET_TransportThread::Add(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCAdd,
+ FNET_Context(comp));
+}
+
+
+void
+FNET_TransportThread::EnableRead(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCEnableRead,
+ FNET_Context(comp));
+}
+
+
+void
+FNET_TransportThread::DisableRead(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCDisableRead,
+ FNET_Context(comp));
+}
+
+
+void
+FNET_TransportThread::EnableWrite(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCEnableWrite,
+ FNET_Context(comp));
+}
+
+
+void
+FNET_TransportThread::DisableWrite(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCDisableWrite,
+ FNET_Context(comp));
+}
+
+
+void
+FNET_TransportThread::Close(FNET_IOComponent *comp, bool needRef)
+{
+ if (needRef) {
+ comp->AddRef();
+ }
+ PostEvent(&FNET_ControlPacket::IOCClose,
+ FNET_Context(comp));
+}
+
+
+bool
+FNET_TransportThread::execute(FNET_IExecutable *exe)
+{
+ return PostEvent(&FNET_ControlPacket::Execute, FNET_Context(exe));
+}
+
+
+void
+FNET_TransportThread::sync()
+{
+ Sync exe;
+ if (execute(&exe)) {
+ exe.gate.await();
+ } else {
+ WaitFinished();
+ }
+}
+
+
+void
+FNET_TransportThread::ShutDown(bool waitFinished)
+{
+ bool wasEmpty = false;
+ Lock();
+ if (!_shutdown) {
+ _shutdown = true;
+ wasEmpty = _queue.IsEmpty_NoLock();
+ }
+ Unlock();
+ if (wasEmpty)
+ _socketEvent.AsyncWakeUp();
+
+ if (waitFinished)
+ WaitFinished();
+}
+
+
+void
+FNET_TransportThread::WaitFinished()
+{
+ if (_finished)
+ return;
+
+ Lock();
+ _waitFinished = true;
+ while (!_finished)
+ Wait();
+ Unlock();
+}
+
+
+bool
+FNET_TransportThread::InitEventLoop()
+{
+ bool wasStarted;
+ bool wasDeleted;
+ Lock();
+ wasStarted = _started;
+ wasDeleted = _deleted;
+ if (!_started && !_deleted) {
+ _started = true;
+ }
+ Unlock();
+ if (wasStarted) {
+ LOG(error, "Transport: InitEventLoop: object already active!");
+ return false;
+ }
+ if (wasDeleted) {
+ LOG(error, "Transport: InitEventLoop: object was deleted!");
+ return false;
+ }
+
+ _events = new FastOS_IOEvent[EVT_MAX];
+ assert(_events != NULL);
+
+ _now.SetNow();
+ _startTime = _now;
+ _statTime = _now;
+ _statsTask.Schedule(5.0);
+ return true;
+}
+
+
+bool
+FNET_TransportThread::EventLoopIteration()
+{
+ FNET_Packet *packet = NULL;
+ FNET_Context context;
+ FNET_IOComponent *component = NULL;
+ int evt_cnt = 0;
+ FastOS_IOEvent *events = _events;
+ int msTimeout = FNET_Scheduler::SLOT_TICK;
+ bool wakeUp = false;
+
+#ifdef FNET_SANITY_CHECKS
+ FastOS_Time beforeGetEvents;
+#endif
+
+ if (!_shutdown) {
+
+#ifdef FNET_SANITY_CHECKS
+ // Warn if event loop takes more than 250ms
+ beforeGetEvents.SetNow();
+ double loopTime = beforeGetEvents.MilliSecs() - _now.MilliSecs();
+ if (loopTime > 250.0)
+ LOG(warning, "SANITY: Transport loop time: %.2f ms", loopTime);
+#endif
+
+ // obtain I/O events
+ evt_cnt = _socketEvent.GetEvents(&wakeUp, msTimeout, events, EVT_MAX);
+ CountEventLoop();
+
+ // sample current time (performed once per event loop iteration)
+ _now.SetNow();
+
+#ifdef FNET_SANITY_CHECKS
+ // Warn if event extraction takes more than timeout + 100ms
+ double extractTime = _now.MilliSecs() - beforeGetEvents.MilliSecs();
+ if (extractTime > (double) msTimeout + 100.0)
+ LOG(warning, "SANITY: Event extraction time: %.2f ms (timeout: %d ms)",
+ extractTime, msTimeout);
+#endif
+
+ // report event error (if any)
+ if (evt_cnt < 0) {
+ std::string str = FastOS_Socket::getLastErrorString();
+ LOG(spam, "Transport: event error: %s", str.c_str());
+ } else {
+ CountIOEvent(evt_cnt);
+ }
+
+ // handle internal transport layer events
+ if (wakeUp) {
+
+ Lock();
+ CountEvent(_queue.FlushPackets_NoLock(&_myQueue));
+ Unlock();
+
+ while ((packet = _myQueue.DequeuePacket_NoLock(&context)) != NULL) {
+
+ if (context._value.IOC->_flags._ioc_delete) {
+ context._value.IOC->SubRef();
+ continue;
+ }
+
+ switch (packet->GetCommand()) {
+ case FNET_ControlPacket::FNET_CMD_IOC_ADD:
+ if (context._value.IOC->_flags._ioc_want_close) {
+ context._value.IOC->Close();
+ context._value.IOC->SubRef();
+ } else {
+ AddComponent(context._value.IOC);
+ context._value.IOC->_flags._ioc_added = true;
+ context._value.IOC->SetSocketEvent(&_socketEvent);
+ }
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_ENABLE_READ:
+ context._value.IOC->EnableReadEvent(true);
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_DISABLE_READ:
+ context._value.IOC->EnableReadEvent(false);
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_ENABLE_WRITE:
+ context._value.IOC->EnableWriteEvent(true);
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_DISABLE_WRITE:
+ context._value.IOC->EnableWriteEvent(false);
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_IOC_CLOSE:
+ if (context._value.IOC->_flags._ioc_added) {
+ RemoveComponent(context._value.IOC);
+ context._value.IOC->Close();
+ AddDeleteComponent(context._value.IOC);
+ } else {
+ context._value.IOC->_flags._ioc_want_close = true;
+ }
+ context._value.IOC->SubRef();
+ break;
+ case FNET_ControlPacket::FNET_CMD_EXECUTE:
+ context._value.EXECUTABLE->execute();
+ break;
+ }
+ }
+ }
+
+ // handle I/O events
+ for (int i = 0; i < evt_cnt; i++) {
+
+ component = (FNET_IOComponent *) events[i]._eventAttribute;
+ if (component == NULL || component->_flags._ioc_delete)
+ continue;
+
+ bool rc = true;
+ if (events[i]._readOccurred)
+ rc = rc && component->HandleReadEvent();
+ if (events[i]._writeOccurred)
+ rc = rc && component->HandleWriteEvent();
+ if (!rc) { // IOC is broken, close it
+ RemoveComponent(component);
+ component->Close();
+ AddDeleteComponent(component);
+ }
+ }
+
+ // handle IOC time-outs
+ if (_config._iocTimeOut > 0) {
+
+ FastOS_Time t = _now;
+ t.SubtractMilliSecs((double)_config._iocTimeOut);
+ fastos::TimeStamp oldest(t);
+ while (_timeOutHead != NULL &&
+ oldest >= _timeOutHead->_ioc_timestamp) {
+
+ component = _timeOutHead;
+ RemoveComponent(component);
+ component->Close();
+ AddDeleteComponent(component);
+ }
+ }
+
+ // perform pending tasks
+ _scheduler.CheckTasks();
+
+ // perform scheduled delete operations
+ FlushDeleteList();
+ } // -- END OF MAIN EVENT LOOP --
+
+ if (!_shutdown)
+ return true;
+ if (_finished)
+ return false;
+
+ // unschedule statistics task
+ _statsTask.Kill();
+
+ // flush event queue
+ Lock();
+ _queue.FlushPackets_NoLock(&_myQueue);
+ Unlock();
+
+ // discard remaining events
+ while ((packet = _myQueue.DequeuePacket_NoLock(&context)) != NULL) {
+ if (packet->GetCommand() == FNET_ControlPacket::FNET_CMD_EXECUTE) {
+ context._value.EXECUTABLE->execute();
+ } else {
+ DiscardEvent((FNET_ControlPacket *)packet, context);
+ }
+ }
+
+ // close and remove all I/O Components
+ component = _componentsHead;
+ while (component != NULL) {
+ assert(component == _componentsHead);
+ FNET_IOComponent *tmp = component;
+ component = component->_ioc_next;
+ RemoveComponent(tmp);
+ tmp->Close();
+ tmp->SubRef();
+ }
+ assert(_componentsHead == NULL &&
+ _componentsTail == NULL &&
+ _timeOutHead == NULL &&
+ _componentCnt == 0 &&
+ _queue.IsEmpty_NoLock() &&
+ _myQueue.IsEmpty_NoLock());
+
+ delete [] _events;
+
+ Lock();
+ _finished = true;
+ if (_waitFinished)
+ Broadcast();
+ Unlock();
+
+ LOG(spam, "Transport: event loop finished.");
+
+ return false;
+}
+
+
+bool
+FNET_TransportThread::Start(FastOS_ThreadPool *pool)
+{
+ return (pool != NULL && pool->NewThread(this));
+}
+
+
+void
+FNET_TransportThread::Main()
+{
+ Run(NULL, NULL);
+}
+
+
+void
+FNET_TransportThread::Run(FastOS_ThreadInterface *thisThread, void *)
+{
+ if (!InitEventLoop()) {
+ LOG(warning, "Transport: Run: Could not init event loop");
+ return;
+ }
+ while (EventLoopIteration()) {
+ if (thisThread != NULL && thisThread->GetBreakFlag())
+ ShutDown(false);
+ }
+}
diff --git a/fnet/src/vespa/fnet/transport_thread.h b/fnet/src/vespa/fnet/transport_thread.h
new file mode 100644
index 00000000000..e7a171f791b
--- /dev/null
+++ b/fnet/src/vespa/fnet/transport_thread.h
@@ -0,0 +1,614 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+/**
+ * This class represents a transport thread and handles a subset of
+ * the network related work for the application in both client and
+ * server aspects.
+ **/
+class FNET_TransportThread : public FastOS_Runnable
+{
+ friend class FNET_IOComponent;
+
+public:
+ enum {
+ EVT_MAX = 4096
+ };
+
+#ifndef IAM_DOXYGEN
+ class StatsTask : public FNET_Task
+ {
+ private:
+ FNET_TransportThread *_transport;
+ StatsTask(const StatsTask &);
+ StatsTask &operator=(const StatsTask &);
+ public:
+ StatsTask(FNET_Scheduler *scheduler,
+ FNET_TransportThread *transport) : FNET_Task(scheduler),
+ _transport(transport) {}
+ virtual void PerformTask();
+ };
+ friend class FNET_TransportThread::StatsTask;
+#endif // DOXYGEN
+
+private:
+ FNET_Transport &_owner; // owning transport layer
+ FastOS_Time _startTime; // when event loop started
+ FastOS_Time _now; // current time sampler
+ FNET_Scheduler _scheduler; // transport thread scheduler
+ FNET_StatCounters _counters; // stat counters
+ FNET_Stats _stats; // current stats
+ StatsTask _statsTask; // stats task
+ FastOS_Time _statTime; // last stat update
+ FNET_Config _config; // FNET configuration [static]
+ FNET_IOComponent *_componentsHead; // I/O component list head
+ FNET_IOComponent *_timeOutHead; // first IOC in list to time out
+ FNET_IOComponent *_componentsTail; // I/O component list tail
+ uint32_t _componentCnt; // # of components
+ FNET_IOComponent *_deleteList; // IOC delete list
+ FastOS_SocketEvent _socketEvent; // I/O event generator
+ FastOS_IOEvent *_events; // I/O event array
+ FNET_PacketQueue_NoLock _queue; // outer event queue
+ FNET_PacketQueue_NoLock _myQueue; // inner event queue
+ FNET_Cond _cond; // used for synchronization
+ bool _started; // event loop started ?
+ bool _shutdown; // should stop event loop ?
+ bool _finished; // event loop stopped ?
+ bool _waitFinished; // someone is waiting for _finished
+ bool _deleted; // destructor called ?
+
+
+ FNET_TransportThread(const FNET_TransportThread &);
+ FNET_TransportThread &operator=(const FNET_TransportThread &);
+
+
+ /**
+ * Lock this object.
+ **/
+ void Lock() { _cond.Lock(); }
+
+
+ /**
+ * Unlock this object.
+ **/
+ void Unlock() { _cond.Unlock(); }
+
+
+ /**
+ * Wait on this object.
+ **/
+ void Wait() { _cond.Wait(); }
+
+
+ /**
+ * Wake all waiting on this object.
+ **/
+ void Broadcast() { _cond.Broadcast(); }
+
+
+ /**
+ * Add an IOComponent to the list of components. This operation is
+ * performed immidiately and without locking. This method should
+ * only be called in the transport thread.
+ *
+ * @param comp the component to add.
+ **/
+ void AddComponent(FNET_IOComponent *comp);
+
+
+ /**
+ * Remove an IOComponent from the list of components. This operation is
+ * performed immidiately and without locking. This method should
+ * only be called in the transport thread.
+ *
+ * @param comp the component to remove.
+ **/
+ void RemoveComponent(FNET_IOComponent *comp);
+
+
+ /**
+ * Update time-out information for the given I/O component. This
+ * method may only be called in the transport thread. Calling this
+ * method will update the timestamp on the given IOC and perform a
+ * remove/add IOC operation, putting it last in the time-out queue.
+ *
+ * @param comp component to update time-out info for.
+ **/
+ void UpdateTimeOut(FNET_IOComponent *comp);
+
+
+ /**
+ * Add an IOComponent to the delete list. This operation is
+ * performed immidiately and without locking. This method should
+ * only be called in the transport thread. NOTE: the IOC must be
+ * removed from the list of active components before this method may
+ * be called.
+ *
+ * @param comp the component to add to the delete list.
+ **/
+ void AddDeleteComponent(FNET_IOComponent *comp);
+
+
+ /**
+ * Delete (call SubRef on) all IO Components in the delete list.
+ **/
+ void FlushDeleteList();
+
+
+ /**
+ * Post an event (ControlPacket) on the transport thread event
+ * queue. This is done to tell the transport thread that it needs to
+ * do an operation that could not be performed in other threads due
+ * to thread-safety. If the event queue is empty, invoking this
+ * method will wake up the transport thread in order to reduce
+ * latency. Note that when posting events that have a reference
+ * counted object as parameter you need to increase the reference
+ * counter to ensure that the object will not be deleted before the
+ * event is handled.
+ *
+ * @return true if the event was accepted (false if rejected)
+ * @param cpacket the event command
+ * @param context the event parameter
+ **/
+ bool PostEvent(FNET_ControlPacket *cpacket, FNET_Context context);
+
+
+ /**
+ * Discard an event. This method is used to discard events that will
+ * not be handled due to shutdown.
+ *
+ * @param cpacket the event command
+ * @param context the event parameter
+ **/
+ void DiscardEvent(FNET_ControlPacket *cpacket, FNET_Context context);
+
+
+ /**
+ * Update internal FNET statistics. This method is called regularly
+ * by the statistics update task.
+ **/
+ void UpdateStats();
+
+
+ /**
+ * Obtain a reference to the stat counters used by this transport
+ * object.
+ *
+ * @return stat counters for this transport object.
+ **/
+ FNET_StatCounters *GetStatCounters() { return &_counters; }
+
+
+ /**
+ * Count event loop iteration(s).
+ *
+ * @param cnt event loop iterations (default is 1).
+ **/
+ void CountEventLoop(uint32_t cnt = 1)
+ { _counters.CountEventLoop(cnt); }
+
+
+ /**
+ * Count internal event(s).
+ *
+ * @param cnt number of internal events.
+ **/
+ void CountEvent(uint32_t cnt)
+ { _counters.CountEvent(cnt); }
+
+
+ /**
+ * Count IO events.
+ *
+ * @param cnt number of IO events.
+ **/
+ void CountIOEvent(uint32_t cnt)
+ { _counters.CountIOEvent(cnt); }
+
+
+ /**
+ * Obtain a reference to the object holding the configuration for
+ * this transport object.
+ *
+ * @return config object.
+ **/
+ FNET_Config *GetConfig() { return &_config; }
+
+
+public:
+ /**
+ * Construct a transport object. To activate your newly created
+ * transport object you need to call either the Start method to
+ * spawn a new thread to handle IO, or the Main method to let the
+ * current thread become the transport thread.
+ *
+ * @param owner owning transport layer
+ **/
+ FNET_TransportThread(FNET_Transport &owner_in);
+
+
+ /**
+ * Destruct object. This should NOT be done before the transport
+ * thread has completed it's work and raised the finished flag.
+ **/
+ ~FNET_TransportThread();
+
+
+ /**
+ * Obtain the owning transport layer
+ *
+ * @return transport layer owning this transport thread
+ **/
+ FNET_Transport &owner() const { return _owner; }
+
+
+ /**
+ * Add a network listener in an abstract way. The given 'spec'
+ * string has the following format: 'type/where'. 'type' specifies
+ * the protocol used; currently only 'tcp' is allowed, but it is
+ * included for future extensions. 'where' specifies where to listen
+ * in a way depending on the 'type' field; with tcp this field holds
+ * a port number. Example: listen for tcp/ip connections on port
+ * 8001: spec = 'tcp/8001'. If you want to enable strict binding you
+ * may supply a hostname as well, like this:
+ * 'tcp/mycomputer.mydomain:8001'.
+ *
+ * @return the connector object, or NULL if listen failed.
+ * @param spec string specifying how and where to listen.
+ * @param streamer custom packet streamer.
+ * @param serverAdapter object for custom channel creation.
+ **/
+ FNET_Connector *Listen(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IServerAdapter *serverAdapter);
+
+
+ /**
+ * Connect to a target host in an abstract way. The given 'spec'
+ * string has the following format: 'type/where'. 'type' specifies
+ * the protocol used; currently only 'tcp' is allowed, but it is
+ * included for future extensions. 'where' specifies where to
+ * connect in a way depending on the type field; with tcp this field
+ * holds a host name (or IP address) and a port number. Example:
+ * connect to www.fast.no on port 80 (using tcp/ip): spec =
+ * 'tcp/www.fast.no:80'. The newly created connection will be
+ * serviced by this transport layer object. If the adminHandler
+ * parameter is given, an internal admin channel is created in the
+ * connection object. The admin channel will be used to deliver
+ * packets tagged with the reserved channel id (FNET_NOID) to the
+ * admin handler.
+ *
+ * @return an object representing the new connection.
+ * @param spec string specifying how and where to connect.
+ * @param streamer custom packet streamer.
+ * @param adminHandler packet handler for incoming packets on the
+ * admin channel.
+ * @param adminContext application context to be used for incoming
+ * packets on the admin channel.
+ * @param serverAdapter adapter used to support 2way channel creation.
+ * @param connContext application context for the connection.
+ **/
+ FNET_Connection *Connect(const char *spec, FNET_IPacketStreamer *streamer,
+ FNET_IPacketHandler *adminHandler = NULL,
+ FNET_Context adminContext = FNET_Context(),
+ FNET_IServerAdapter *serverAdapter = NULL,
+ FNET_Context connContext = FNET_Context());
+
+
+ /**
+ * This method may be used to determine how many IO Components are
+ * currently controlled by this transport layer object. Note that
+ * locking is not used, since this information is volatile anyway.
+ *
+ * @return the current number of IOComponents.
+ **/
+ uint32_t GetNumIOComponents() { return _componentCnt; }
+
+
+ /**
+ * Set the I/O Component timeout. Idle I/O Components with timeout
+ * enabled (determined by calling the ShouldTimeOut method) will
+ * time out if idle for the given number of milliseconds. An I/O
+ * component reports its un-idle-ness by calling the UpdateTimeOut
+ * method in the owning transport object. Calling this method with 0
+ * as parameter will disable I/O Component timeouts. Note that newly
+ * created transport objects begin their lives with I/O Component
+ * timeouts disabled. An I/O Component timeout has the same effect
+ * as calling the Close method in the transport object with the
+ * target I/O Component as parameter.
+ *
+ * @param ms number of milliseconds before IOC idle timeout occurs.
+ **/
+ void SetIOCTimeOut(uint32_t ms) { _config._iocTimeOut = ms; }
+
+
+ /**
+ * Set maximum input buffer size. This value will only affect
+ * connections that use a common input buffer when decoding
+ * incoming packets. Note that this value is not an absolute
+ * max. The buffer will still grow larger than this value if
+ * needed to decode big packets. However, when the buffer becomes
+ * larger than this value, it will be shrunk back when possible.
+ *
+ * @param bytes buffer size in bytes. 0 means unlimited.
+ **/
+ void SetMaxInputBufferSize(uint32_t bytes)
+ { _config._maxInputBufferSize = bytes; }
+
+
+ /**
+ * Set maximum output buffer size. This value will only affect
+ * connections that use a common output buffer when encoding
+ * outgoing packets. Note that this value is not an absolute
+ * max. The buffer will still grow larger than this value if needed
+ * to encode big packets. However, when the buffer becomes larger
+ * than this value, it will be shrunk back when possible.
+ *
+ * @param bytes buffer size in bytes. 0 means unlimited.
+ **/
+ void SetMaxOutputBufferSize(uint32_t bytes)
+ { _config._maxOutputBufferSize = bytes; }
+
+
+ /**
+ * Enable or disable the direct write optimization. This is
+ * enabled by default and favors low latency above throughput.
+ *
+ * @param directWrite enable direct write?
+ **/
+ void SetDirectWrite(bool directWrite) {
+ _config._directWrite = directWrite;
+ }
+
+
+ /**
+ * Enable or disable use of the TCP_NODELAY flag with sockets
+ * created by this transport object.
+ *
+ * @param noDelay true if TCP_NODELAY flag should be used.
+ **/
+ void SetTCPNoDelay(bool noDelay) { _config._tcpNoDelay = noDelay; }
+
+
+ /**
+ * Enable or disable logging of FNET statistics. This feature is
+ * disabled by default.
+ *
+ * @param logStats true if stats should be logged.
+ **/
+ void SetLogStats(bool logStats) { _config._logStats = logStats; }
+
+
+ /**
+ * Add an I/O component to the working set of this transport
+ * object. Note that the actual work is performed by the transport
+ * thread. This method simply posts an event on the transport thread
+ * event queue. NOTE: in order to post async events regarding I/O
+ * components, an extra reference to the component needs to be
+ * allocated. The needRef flag indicates wether the caller already
+ * has done this.
+ *
+ * @param comp the component you want to add.
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void Add(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Calling this method enables read events for the given I/O
+ * component. Note that the actual work is performed by the
+ * transport thread. This method simply posts an event on the
+ * transport thread event queue. NOTE: in order to post async events
+ * regarding I/O components, an extra reference to the component
+ * needs to be allocated. The needRef flag indicates wether the
+ * caller already has done this.
+ *
+ * @param comp the component that wants read events.
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void EnableRead(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Calling this method disables read events for the given I/O
+ * component. Note that the actual work is performed by the
+ * transport thread. This method simply posts an event on the
+ * transport thread event queue. NOTE: in order to post async events
+ * regarding I/O components, an extra reference to the component
+ * needs to be allocated. The needRef flag indicates wether the
+ * caller already has done this.
+ *
+ * @param comp the component that no longer wants read events.
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void DisableRead(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Calling this method enables write events for the given I/O
+ * component. Note that the actual work is performed by the
+ * transport thread. This method simply posts an event on the
+ * transport thread event queue. NOTE: in order to post async events
+ * regarding I/O components, an extra reference to the component
+ * needs to be allocated. The needRef flag indicates wether the
+ * caller already has done this.
+ *
+ * @param comp the component that wants write events.
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void EnableWrite(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Calling this method disables write events for the given I/O
+ * component. Note that the actual work is performed by the
+ * transport thread. This method simply posts an event on the
+ * transport thread event queue. NOTE: in order to post async events
+ * regarding I/O components, an extra reference to the component
+ * needs to be allocated. The needRef flag indicates wether the
+ * caller already has done this.
+ *
+ * @param comp the component that no longer wants write events.
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void DisableWrite(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Close an I/O component and remove it from the working set of this
+ * transport object. Note that the actual work is performed by the
+ * transport thread. This method simply posts an event on the
+ * transport thread event queue. NOTE: in order to post async events
+ * regarding I/O components, an extra reference to the component
+ * needs to be allocated. The needRef flag indicates wether the
+ * caller already has done this.
+ *
+ * @param comp the component you want to close (and remove).
+ * @param needRef should be set to false if the caller of this
+ * method already has obtained an extra reference to the
+ * component. If this flag is true, this method will call the
+ * AddRef method on the component.
+ **/
+ void Close(FNET_IOComponent *comp, bool needRef = true);
+
+
+ /**
+ * Post an execution event on the transport event queue. The return
+ * value from this method indicate whether the execution request was
+ * accepted or not. If it was accepted, the transport thread will
+ * execute the given executable at a later time. However, if it was
+ * rejected (this method returns false), the caller of this method
+ * will need to handle the fact that the executor will never be
+ * executed. Also note that it is the responsibility of the caller
+ * to ensure that all needed context for the executor is kept alive
+ * until the time of execution. It is ok to assume that execution
+ * requests will only be rejected due to transport thread shutdown.
+ *
+ * @return true if the execution request was accepted, false if it was rejected
+ * @param exe the executable we want to execute in the transport thread
+ **/
+ bool execute(FNET_IExecutable *exe);
+
+
+ /**
+ * Synchronize with the transport thread. This method will block
+ * until all events posted before this method was invoked has been
+ * processed. If the transport thread has been shut down (or is in
+ * the progress of being shut down) this method will instead wait
+ * for the transport thread to complete, since no more commands will
+ * be performed, and waiting would be forever. Invoking this method
+ * from the transport thread is not a good idea.
+ **/
+ void sync();
+
+
+ /**
+ * Obtain a pointer to the current time sampler. The current time
+ * sampler may only be used by the transport thread. Also, it SHOULD
+ * be used by ALL methods driven by the transport thread that wants
+ * to have an estimate of the current time. This includes the custom
+ * application hook, packet delivery callbacks and pingable objects.
+ **/
+ FastOS_Time *GetTimeSampler() { return &_now; }
+
+
+ /**
+ * Obtain a pointer to the transport thread scheduler. This
+ * scheduler may be used to schedule tasks to be run by the
+ * transport thread.
+ *
+ * @return transport thread scheduler.
+ **/
+ FNET_Scheduler *GetScheduler() { return &_scheduler; }
+
+
+ /**
+ * Calling this method will shut down the transport layer in a nice
+ * way. Note that the actual task of shutting down is performed by
+ * the transport thread. This method simply posts an event on the
+ * transport thread event queue telling it to shut down.
+ *
+ * @param waitFinished if this flag is set, the method call will not
+ * return until the transport layer is shut down. NOTE: do
+ * not set this flag if you are calling this method from a
+ * callback from the transport layer, as it will create a
+ * deadlock.
+ **/
+ void ShutDown(bool waitFinished);
+
+
+ /**
+ * This method will make the calling thread wait until the transport
+ * layer has been shut down. NOTE: do not invoke this method if you
+ * are in a callback from the transport layer, as it will create a
+ * deadlock. See @ref ShutDown.
+ **/
+ void WaitFinished();
+
+
+ /**
+ * This method is called to initialize the transport thread event
+ * loop. It is called from the FRT_Transport::Run method. If you
+ * want to customize the event loop, you should do this by invoking
+ * this method once, then invoke the @ref EventLoopIteration method
+ * until it returns false (indicating transport shutdown).
+ *
+ * @return true on success, false on failure.
+ **/
+ bool InitEventLoop();
+
+
+ /**
+ * Perform a single transport thread event loop iteration. This
+ * method is called by the FRT_Transport::Run method. If you want to
+ * customize the event loop, you should do this by invoking the @ref
+ * InitEventLoop method once, then invoke this method until it
+ * returns false (indicating transport shutdown).
+ *
+ * @return true when active, false after shutdown.
+ **/
+ bool EventLoopIteration();
+
+
+ /**
+ * Start transport layer operation in a separate thread. Note that
+ * the return value of this method only indicates whether the
+ * spawning of the new thread went ok.
+ *
+ * @return thread create status.
+ * @param pool threadpool that may be used to spawn a new thread.
+ **/
+ bool Start(FastOS_ThreadPool *pool);
+
+
+ /**
+ * Calling this method will give the current thread to the transport
+ * layer. The method will not return until the transport layer is
+ * shut down by calling the @ref ShutDown method.
+ **/
+ void Main();
+
+
+ /**
+ * This is where the transport thread lives, when started by
+ * invoking one of the @ref Main or @ref Start methods. If you want
+ * to combine the FNET event loop with your own, you may use the
+ * @ref InitEventLoop and @ref EventLoopIteration methods directly.
+ **/
+ void Run(FastOS_ThreadInterface *thisThread, void *args);
+};
diff --git a/fnet/src/vespa/fnet/vtag.cpp b/fnet/src/vespa/fnet/vtag.cpp
new file mode 100644
index 00000000000..e2f280dee5b
--- /dev/null
+++ b/fnet/src/vespa/fnet/vtag.cpp
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/fnet/fnet.h>
+
+#ifndef V_TAG
+#define V_TAG "NOTAG"
+#define V_TAG_DATE "NOTAG"
+#define V_TAG_SYSTEM "NOTAG"
+#define V_TAG_SYSTEM_REV "NOTAG"
+#define V_TAG_BUILDER "NOTAG"
+#endif
+
+namespace fnet {
+
+char VersionTag[] = V_TAG;
+char VersionTagDate[] = V_TAG_DATE;
+char VersionTagSystem[] = V_TAG_SYSTEM;
+char VersionTagSystemRev[] = V_TAG_SYSTEM_REV;
+char VersionTagBuilder[] = V_TAG_BUILDER;
+
+} // namespace fnet
diff --git a/fnet/src/vespa/fnet/vtag.h b/fnet/src/vespa/fnet/vtag.h
new file mode 100644
index 00000000000..28b3164e9fc
--- /dev/null
+++ b/fnet/src/vespa/fnet/vtag.h
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+namespace fnet {
+
+extern char VersionTag[];
+extern char VersionTagDate[];
+extern char VersionTagSystem[];
+extern char VersionTagSystemRev[];
+extern char VersionTagBuilder[];
+
+} // namespace fnet
+
diff --git a/fnet/testrun/.gitignore b/fnet/testrun/.gitignore
new file mode 100644
index 00000000000..faed45bc94a
--- /dev/null
+++ b/fnet/testrun/.gitignore
@@ -0,0 +1,10 @@
+test-report.html
+test-report.html.*
+test.*.*.desc
+test.*.*.file.*
+test.*.*.files.html
+test.*.*.log
+tmp.*
+xsync.log
+/test.*.*.result
+Makefile