From 72231250ed81e10d66bfe70701e64fa5fe50f712 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 15 Jun 2016 23:09:44 +0200 Subject: Publish --- fnet/.gitignore | 10 + fnet/CMakeLists.txt | 34 + fnet/INSTALL | 22 + fnet/OWNERS | 2 + fnet/README | 26 + fnet/RELEASEINFO | 788 +++++++++++ fnet/build/buildspec.xml | 39 + fnet/ethereal/Makefile.am | 26 + fnet/ethereal/Makefile.nmake | 27 + fnet/ethereal/moduleinfo.h | 18 + fnet/ethereal/packet-fnetrpc.c | 389 ++++++ fnet/index.html | 15 + fnet/src/.gitignore | 10 + fnet/src/Doxyfile | 939 +++++++++++++ fnet/src/examples/frt/rpc/.gitignore | 16 + fnet/src/examples/frt/rpc/CMakeLists.txt | 60 + fnet/src/examples/frt/rpc/echo_client.cpp | 94 ++ fnet/src/examples/frt/rpc/rpc_callback_client.cpp | 107 ++ fnet/src/examples/frt/rpc/rpc_callback_server.cpp | 69 + fnet/src/examples/frt/rpc/rpc_client.cpp | 92 ++ fnet/src/examples/frt/rpc/rpc_info.cpp | 140 ++ fnet/src/examples/frt/rpc/rpc_invoke.cpp | 107 ++ fnet/src/examples/frt/rpc/rpc_proxy.cpp | 254 ++++ fnet/src/examples/frt/rpc/rpc_server.cpp | 125 ++ fnet/src/examples/ping/.gitignore | 9 + fnet/src/examples/ping/CMakeLists.txt | 17 + fnet/src/examples/ping/packets.cpp | 65 + fnet/src/examples/ping/packets.h | 36 + fnet/src/examples/ping/pingclient.cpp | 93 ++ fnet/src/examples/ping/pingserver.cpp | 65 + fnet/src/examples/proxy/.gitignore | 4 + fnet/src/examples/proxy/CMakeLists.txt | 8 + fnet/src/examples/proxy/proxy.cpp | 244 ++++ fnet/src/examples/test/.gitignore | 0 fnet/src/examples/timeout/.gitignore | 4 + fnet/src/examples/timeout/CMakeLists.txt | 8 + fnet/src/examples/timeout/timeout.cpp | 93 ++ fnet/src/testlist.txt | 17 + fnet/src/tests/.gitignore | 3 + fnet/src/tests/connect_thread/.gitignore | 1 + fnet/src/tests/connect_thread/CMakeLists.txt | 8 + .../tests/connect_thread/connect_thread_test.cpp | 26 + fnet/src/tests/connection_spread/.gitignore | 1 + fnet/src/tests/connection_spread/CMakeLists.txt | 8 + .../connection_spread/connection_spread_test.cpp | 83 ++ fnet/src/tests/databuffer/.gitignore | 4 + fnet/src/tests/databuffer/CMakeLists.txt | 8 + fnet/src/tests/databuffer/DESC | 2 + fnet/src/tests/databuffer/FILES | 1 + fnet/src/tests/databuffer/databuffer.cpp | 201 +++ fnet/src/tests/examples/.gitignore | 1 + fnet/src/tests/examples/CMakeLists.txt | 8 + fnet/src/tests/examples/FILES | 1 + fnet/src/tests/examples/examples_test.cpp | 246 ++++ fnet/src/tests/fdselector/.gitignore | 4 + fnet/src/tests/fdselector/CMakeLists.txt | 8 + fnet/src/tests/fdselector/DESC | 1 + fnet/src/tests/fdselector/FILES | 1 + fnet/src/tests/fdselector/fdselector.cpp | 220 +++ fnet/src/tests/frt/memorytub/.gitignore | 6 + fnet/src/tests/frt/memorytub/CMakeLists.txt | 8 + fnet/src/tests/frt/memorytub/DESC | 1 + fnet/src/tests/frt/memorytub/FILES | 1 + fnet/src/tests/frt/memorytub/memorytub.cpp | 124 ++ fnet/src/tests/frt/method_pt/.gitignore | 6 + fnet/src/tests/frt/method_pt/CMakeLists.txt | 8 + fnet/src/tests/frt/method_pt/DESC | 2 + fnet/src/tests/frt/method_pt/FILES | 1 + fnet/src/tests/frt/method_pt/method_pt.cpp | 395 ++++++ fnet/src/tests/frt/parallel_rpc/.gitignore | 1 + fnet/src/tests/frt/parallel_rpc/CMakeLists.txt | 8 + .../tests/frt/parallel_rpc/parallel_rpc_test.cpp | 129 ++ fnet/src/tests/frt/rpc/.gitignore | 12 + fnet/src/tests/frt/rpc/CMakeLists.txt | 29 + fnet/src/tests/frt/rpc/DESC | 1 + fnet/src/tests/frt/rpc/FILES | 3 + fnet/src/tests/frt/rpc/detach_return_invoke.cpp | 69 + fnet/src/tests/frt/rpc/invoke.cpp | 938 +++++++++++++ fnet/src/tests/frt/rpc/session.cpp | 124 ++ fnet/src/tests/frt/rpc/sharedblob.cpp | 256 ++++ fnet/src/tests/frt/values/.gitignore | 1 + fnet/src/tests/frt/values/CMakeLists.txt | 8 + fnet/src/tests/frt/values/FILES | 1 + fnet/src/tests/frt/values/values_test.cpp | 207 +++ fnet/src/tests/info/.gitignore | 4 + fnet/src/tests/info/CMakeLists.txt | 8 + fnet/src/tests/info/DESC | 1 + fnet/src/tests/info/FILES | 1 + fnet/src/tests/info/info.cpp | 89 ++ fnet/src/tests/locking/.gitignore | 8 + fnet/src/tests/locking/CMakeLists.txt | 23 + fnet/src/tests/locking/DESC | 1 + fnet/src/tests/locking/FILES | 2 + fnet/src/tests/locking/castspeed.cpp | 219 +++ fnet/src/tests/locking/drainpackets.cpp | 134 ++ fnet/src/tests/locking/dummy.cpp | 9 + fnet/src/tests/locking/dummy.h | 17 + fnet/src/tests/locking/lockspeed.cpp | 177 +++ fnet/src/tests/printstuff/.gitignore | 1 + fnet/src/tests/printstuff/CMakeLists.txt | 8 + fnet/src/tests/printstuff/FILES | 1 + fnet/src/tests/printstuff/printstuff_test.cpp | 45 + fnet/src/tests/regress/databuffer/.gitignore | 0 fnet/src/tests/regress/fdselector/.gitignore | 0 fnet/src/tests/regress/frt/memorytub/.gitignore | 0 fnet/src/tests/regress/frt/method_pt/.gitignore | 0 fnet/src/tests/regress/frt/rpc/.gitignore | 0 fnet/src/tests/regress/frt/values/.gitignore | 0 fnet/src/tests/regress/info/.gitignore | 0 fnet/src/tests/regress/locking/.gitignore | 0 fnet/src/tests/regress/scheduling/.gitignore | 0 fnet/src/tests/regress/spiral/.gitignore | 0 fnet/src/tests/regress/sync_execute/.gitignore | 0 fnet/src/tests/regress/thread_id/.gitignore | 0 fnet/src/tests/regress/time/.gitignore | 0 fnet/src/tests/scheduling/.gitignore | 6 + fnet/src/tests/scheduling/CMakeLists.txt | 15 + fnet/src/tests/scheduling/DESC | 1 + fnet/src/tests/scheduling/FILES | 1 + fnet/src/tests/scheduling/schedule.cpp | 153 ++ fnet/src/tests/scheduling/sloweventloop.cpp | 65 + fnet/src/tests/sync_execute/.gitignore | 4 + fnet/src/tests/sync_execute/CMakeLists.txt | 8 + fnet/src/tests/sync_execute/DESC | 1 + fnet/src/tests/sync_execute/FILES | 1 + fnet/src/tests/sync_execute/sync_execute.cpp | 39 + fnet/src/tests/thread_selection/.gitignore | 1 + fnet/src/tests/thread_selection/CMakeLists.txt | 8 + .../thread_selection/thread_selection_test.cpp | 89 ++ fnet/src/tests/time/.gitignore | 4 + fnet/src/tests/time/CMakeLists.txt | 8 + fnet/src/tests/time/DESC | 1 + fnet/src/tests/time/FILES | 1 + fnet/src/tests/time/timespeed.cpp | 68 + fnet/src/vespa/fnet/.gitignore | 6 + fnet/src/vespa/fnet/CMakeLists.txt | 29 + fnet/src/vespa/fnet/channel.cpp | 40 + fnet/src/vespa/fnet/channel.h | 95 ++ fnet/src/vespa/fnet/channellookup.cpp | 66 + fnet/src/vespa/fnet/channellookup.h | 91 ++ fnet/src/vespa/fnet/config.cpp | 22 + fnet/src/vespa/fnet/config.h | 24 + fnet/src/vespa/fnet/connect_thread.cpp | 57 + fnet/src/vespa/fnet/connect_thread.h | 47 + fnet/src/vespa/fnet/connection.cpp | 699 ++++++++++ fnet/src/vespa/fnet/connection.h | 509 +++++++ fnet/src/vespa/fnet/connector.cpp | 101 ++ fnet/src/vespa/fnet/connector.h | 85 ++ fnet/src/vespa/fnet/context.h | 45 + fnet/src/vespa/fnet/controlpacket.cpp | 110 ++ fnet/src/vespa/fnet/controlpacket.h | 101 ++ fnet/src/vespa/fnet/databuffer.cpp | 139 ++ fnet/src/vespa/fnet/databuffer.h | 663 +++++++++ fnet/src/vespa/fnet/dummypacket.cpp | 52 + fnet/src/vespa/fnet/dummypacket.h | 56 + fnet/src/vespa/fnet/fdselector.cpp | 104 ++ fnet/src/vespa/fnet/fdselector.h | 215 +++ fnet/src/vespa/fnet/fnet.h | 142 ++ fnet/src/vespa/fnet/frt/.gitignore | 3 + fnet/src/vespa/fnet/frt/CMakeLists.txt | 14 + fnet/src/vespa/fnet/frt/error.cpp | 61 + fnet/src/vespa/fnet/frt/error.h | 25 + fnet/src/vespa/fnet/frt/frt.h | 37 + fnet/src/vespa/fnet/frt/invokable.h | 14 + fnet/src/vespa/fnet/frt/invoker.cpp | 189 +++ fnet/src/vespa/fnet/frt/invoker.h | 168 +++ fnet/src/vespa/fnet/frt/isharedblob.h | 17 + fnet/src/vespa/fnet/frt/memorytub.cpp | 42 + fnet/src/vespa/fnet/frt/memorytub.h | 147 ++ fnet/src/vespa/fnet/frt/packets.cpp | 271 ++++ fnet/src/vespa/fnet/frt/packets.h | 102 ++ fnet/src/vespa/fnet/frt/reflection.cpp | 198 +++ fnet/src/vespa/fnet/frt/reflection.h | 160 +++ fnet/src/vespa/fnet/frt/rpcrequest.cpp | 119 ++ fnet/src/vespa/fnet/frt/rpcrequest.h | 193 +++ fnet/src/vespa/fnet/frt/supervisor.cpp | 516 +++++++ fnet/src/vespa/fnet/frt/supervisor.h | 137 ++ fnet/src/vespa/fnet/frt/target.cpp | 14 + fnet/src/vespa/fnet/frt/target.h | 56 + fnet/src/vespa/fnet/frt/values.cpp | 1473 ++++++++++++++++++++ fnet/src/vespa/fnet/frt/values.h | 535 +++++++ fnet/src/vespa/fnet/iexecutable.h | 23 + fnet/src/vespa/fnet/info.cpp | 88 ++ fnet/src/vespa/fnet/info.h | 61 + fnet/src/vespa/fnet/iocomponent.cpp | 125 ++ fnet/src/vespa/fnet/iocomponent.h | 324 +++++ fnet/src/vespa/fnet/ipacketfactory.h | 34 + fnet/src/vespa/fnet/ipackethandler.h | 55 + fnet/src/vespa/fnet/ipacketstreamer.h | 80 ++ fnet/src/vespa/fnet/iserveradapter.h | 59 + fnet/src/vespa/fnet/packet.cpp | 16 + fnet/src/vespa/fnet/packet.h | 156 +++ fnet/src/vespa/fnet/packetqueue.cpp | 276 ++++ fnet/src/vespa/fnet/packetqueue.h | 284 ++++ fnet/src/vespa/fnet/scheduler.cpp | 175 +++ fnet/src/vespa/fnet/scheduler.h | 248 ++++ fnet/src/vespa/fnet/signalshutdown.cpp | 25 + fnet/src/vespa/fnet/signalshutdown.h | 25 + fnet/src/vespa/fnet/simplepacketstreamer.cpp | 67 + fnet/src/vespa/fnet/simplepacketstreamer.h | 40 + fnet/src/vespa/fnet/spiral/.gitignore | 0 fnet/src/vespa/fnet/stats.cpp | 98 ++ fnet/src/vespa/fnet/stats.h | 101 ++ fnet/src/vespa/fnet/task.cpp | 54 + fnet/src/vespa/fnet/task.h | 75 + fnet/src/vespa/fnet/testkit/xsync/.gitignore | 0 fnet/src/vespa/fnet/transport.cpp | 163 +++ fnet/src/vespa/fnet/transport.h | 291 ++++ fnet/src/vespa/fnet/transport_thread.cpp | 733 ++++++++++ fnet/src/vespa/fnet/transport_thread.h | 614 ++++++++ fnet/src/vespa/fnet/vtag.cpp | 21 + fnet/src/vespa/fnet/vtag.h | 13 + fnet/testrun/.gitignore | 10 + 213 files changed, 20792 insertions(+) create mode 100644 fnet/.gitignore create mode 100644 fnet/CMakeLists.txt create mode 100644 fnet/INSTALL create mode 100644 fnet/OWNERS create mode 100644 fnet/README create mode 100644 fnet/RELEASEINFO create mode 100644 fnet/build/buildspec.xml create mode 100644 fnet/ethereal/Makefile.am create mode 100644 fnet/ethereal/Makefile.nmake create mode 100644 fnet/ethereal/moduleinfo.h create mode 100644 fnet/ethereal/packet-fnetrpc.c create mode 100644 fnet/index.html create mode 100644 fnet/src/.gitignore create mode 100644 fnet/src/Doxyfile create mode 100644 fnet/src/examples/frt/rpc/.gitignore create mode 100644 fnet/src/examples/frt/rpc/CMakeLists.txt create mode 100644 fnet/src/examples/frt/rpc/echo_client.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_callback_client.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_callback_server.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_client.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_info.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_invoke.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_proxy.cpp create mode 100644 fnet/src/examples/frt/rpc/rpc_server.cpp create mode 100644 fnet/src/examples/ping/.gitignore create mode 100644 fnet/src/examples/ping/CMakeLists.txt create mode 100644 fnet/src/examples/ping/packets.cpp create mode 100644 fnet/src/examples/ping/packets.h create mode 100644 fnet/src/examples/ping/pingclient.cpp create mode 100644 fnet/src/examples/ping/pingserver.cpp create mode 100644 fnet/src/examples/proxy/.gitignore create mode 100644 fnet/src/examples/proxy/CMakeLists.txt create mode 100644 fnet/src/examples/proxy/proxy.cpp create mode 100644 fnet/src/examples/test/.gitignore create mode 100644 fnet/src/examples/timeout/.gitignore create mode 100644 fnet/src/examples/timeout/CMakeLists.txt create mode 100644 fnet/src/examples/timeout/timeout.cpp create mode 100644 fnet/src/testlist.txt create mode 100644 fnet/src/tests/.gitignore create mode 100644 fnet/src/tests/connect_thread/.gitignore create mode 100644 fnet/src/tests/connect_thread/CMakeLists.txt create mode 100644 fnet/src/tests/connect_thread/connect_thread_test.cpp create mode 100644 fnet/src/tests/connection_spread/.gitignore create mode 100644 fnet/src/tests/connection_spread/CMakeLists.txt create mode 100644 fnet/src/tests/connection_spread/connection_spread_test.cpp create mode 100644 fnet/src/tests/databuffer/.gitignore create mode 100644 fnet/src/tests/databuffer/CMakeLists.txt create mode 100644 fnet/src/tests/databuffer/DESC create mode 100644 fnet/src/tests/databuffer/FILES create mode 100644 fnet/src/tests/databuffer/databuffer.cpp create mode 100644 fnet/src/tests/examples/.gitignore create mode 100644 fnet/src/tests/examples/CMakeLists.txt create mode 100644 fnet/src/tests/examples/FILES create mode 100644 fnet/src/tests/examples/examples_test.cpp create mode 100644 fnet/src/tests/fdselector/.gitignore create mode 100644 fnet/src/tests/fdselector/CMakeLists.txt create mode 100644 fnet/src/tests/fdselector/DESC create mode 100644 fnet/src/tests/fdselector/FILES create mode 100644 fnet/src/tests/fdselector/fdselector.cpp create mode 100644 fnet/src/tests/frt/memorytub/.gitignore create mode 100644 fnet/src/tests/frt/memorytub/CMakeLists.txt create mode 100644 fnet/src/tests/frt/memorytub/DESC create mode 100644 fnet/src/tests/frt/memorytub/FILES create mode 100644 fnet/src/tests/frt/memorytub/memorytub.cpp create mode 100644 fnet/src/tests/frt/method_pt/.gitignore create mode 100644 fnet/src/tests/frt/method_pt/CMakeLists.txt create mode 100644 fnet/src/tests/frt/method_pt/DESC create mode 100644 fnet/src/tests/frt/method_pt/FILES create mode 100644 fnet/src/tests/frt/method_pt/method_pt.cpp create mode 100644 fnet/src/tests/frt/parallel_rpc/.gitignore create mode 100644 fnet/src/tests/frt/parallel_rpc/CMakeLists.txt create mode 100644 fnet/src/tests/frt/parallel_rpc/parallel_rpc_test.cpp create mode 100644 fnet/src/tests/frt/rpc/.gitignore create mode 100644 fnet/src/tests/frt/rpc/CMakeLists.txt create mode 100644 fnet/src/tests/frt/rpc/DESC create mode 100644 fnet/src/tests/frt/rpc/FILES create mode 100644 fnet/src/tests/frt/rpc/detach_return_invoke.cpp create mode 100644 fnet/src/tests/frt/rpc/invoke.cpp create mode 100644 fnet/src/tests/frt/rpc/session.cpp create mode 100644 fnet/src/tests/frt/rpc/sharedblob.cpp create mode 100644 fnet/src/tests/frt/values/.gitignore create mode 100644 fnet/src/tests/frt/values/CMakeLists.txt create mode 100644 fnet/src/tests/frt/values/FILES create mode 100644 fnet/src/tests/frt/values/values_test.cpp create mode 100644 fnet/src/tests/info/.gitignore create mode 100644 fnet/src/tests/info/CMakeLists.txt create mode 100644 fnet/src/tests/info/DESC create mode 100644 fnet/src/tests/info/FILES create mode 100644 fnet/src/tests/info/info.cpp create mode 100644 fnet/src/tests/locking/.gitignore create mode 100644 fnet/src/tests/locking/CMakeLists.txt create mode 100644 fnet/src/tests/locking/DESC create mode 100644 fnet/src/tests/locking/FILES create mode 100644 fnet/src/tests/locking/castspeed.cpp create mode 100644 fnet/src/tests/locking/drainpackets.cpp create mode 100644 fnet/src/tests/locking/dummy.cpp create mode 100644 fnet/src/tests/locking/dummy.h create mode 100644 fnet/src/tests/locking/lockspeed.cpp create mode 100644 fnet/src/tests/printstuff/.gitignore create mode 100644 fnet/src/tests/printstuff/CMakeLists.txt create mode 100644 fnet/src/tests/printstuff/FILES create mode 100644 fnet/src/tests/printstuff/printstuff_test.cpp create mode 100644 fnet/src/tests/regress/databuffer/.gitignore create mode 100644 fnet/src/tests/regress/fdselector/.gitignore create mode 100644 fnet/src/tests/regress/frt/memorytub/.gitignore create mode 100644 fnet/src/tests/regress/frt/method_pt/.gitignore create mode 100644 fnet/src/tests/regress/frt/rpc/.gitignore create mode 100644 fnet/src/tests/regress/frt/values/.gitignore create mode 100644 fnet/src/tests/regress/info/.gitignore create mode 100644 fnet/src/tests/regress/locking/.gitignore create mode 100644 fnet/src/tests/regress/scheduling/.gitignore create mode 100644 fnet/src/tests/regress/spiral/.gitignore create mode 100644 fnet/src/tests/regress/sync_execute/.gitignore create mode 100644 fnet/src/tests/regress/thread_id/.gitignore create mode 100644 fnet/src/tests/regress/time/.gitignore create mode 100644 fnet/src/tests/scheduling/.gitignore create mode 100644 fnet/src/tests/scheduling/CMakeLists.txt create mode 100644 fnet/src/tests/scheduling/DESC create mode 100644 fnet/src/tests/scheduling/FILES create mode 100644 fnet/src/tests/scheduling/schedule.cpp create mode 100644 fnet/src/tests/scheduling/sloweventloop.cpp create mode 100644 fnet/src/tests/sync_execute/.gitignore create mode 100644 fnet/src/tests/sync_execute/CMakeLists.txt create mode 100644 fnet/src/tests/sync_execute/DESC create mode 100644 fnet/src/tests/sync_execute/FILES create mode 100644 fnet/src/tests/sync_execute/sync_execute.cpp create mode 100644 fnet/src/tests/thread_selection/.gitignore create mode 100644 fnet/src/tests/thread_selection/CMakeLists.txt create mode 100644 fnet/src/tests/thread_selection/thread_selection_test.cpp create mode 100644 fnet/src/tests/time/.gitignore create mode 100644 fnet/src/tests/time/CMakeLists.txt create mode 100644 fnet/src/tests/time/DESC create mode 100644 fnet/src/tests/time/FILES create mode 100644 fnet/src/tests/time/timespeed.cpp create mode 100644 fnet/src/vespa/fnet/.gitignore create mode 100644 fnet/src/vespa/fnet/CMakeLists.txt create mode 100644 fnet/src/vespa/fnet/channel.cpp create mode 100644 fnet/src/vespa/fnet/channel.h create mode 100644 fnet/src/vespa/fnet/channellookup.cpp create mode 100644 fnet/src/vespa/fnet/channellookup.h create mode 100644 fnet/src/vespa/fnet/config.cpp create mode 100644 fnet/src/vespa/fnet/config.h create mode 100644 fnet/src/vespa/fnet/connect_thread.cpp create mode 100644 fnet/src/vespa/fnet/connect_thread.h create mode 100644 fnet/src/vespa/fnet/connection.cpp create mode 100644 fnet/src/vespa/fnet/connection.h create mode 100644 fnet/src/vespa/fnet/connector.cpp create mode 100644 fnet/src/vespa/fnet/connector.h create mode 100644 fnet/src/vespa/fnet/context.h create mode 100644 fnet/src/vespa/fnet/controlpacket.cpp create mode 100644 fnet/src/vespa/fnet/controlpacket.h create mode 100644 fnet/src/vespa/fnet/databuffer.cpp create mode 100644 fnet/src/vespa/fnet/databuffer.h create mode 100644 fnet/src/vespa/fnet/dummypacket.cpp create mode 100644 fnet/src/vespa/fnet/dummypacket.h create mode 100644 fnet/src/vespa/fnet/fdselector.cpp create mode 100644 fnet/src/vespa/fnet/fdselector.h create mode 100644 fnet/src/vespa/fnet/fnet.h create mode 100644 fnet/src/vespa/fnet/frt/.gitignore create mode 100644 fnet/src/vespa/fnet/frt/CMakeLists.txt create mode 100644 fnet/src/vespa/fnet/frt/error.cpp create mode 100644 fnet/src/vespa/fnet/frt/error.h create mode 100644 fnet/src/vespa/fnet/frt/frt.h create mode 100644 fnet/src/vespa/fnet/frt/invokable.h create mode 100644 fnet/src/vespa/fnet/frt/invoker.cpp create mode 100644 fnet/src/vespa/fnet/frt/invoker.h create mode 100644 fnet/src/vespa/fnet/frt/isharedblob.h create mode 100644 fnet/src/vespa/fnet/frt/memorytub.cpp create mode 100644 fnet/src/vespa/fnet/frt/memorytub.h create mode 100644 fnet/src/vespa/fnet/frt/packets.cpp create mode 100644 fnet/src/vespa/fnet/frt/packets.h create mode 100644 fnet/src/vespa/fnet/frt/reflection.cpp create mode 100644 fnet/src/vespa/fnet/frt/reflection.h create mode 100644 fnet/src/vespa/fnet/frt/rpcrequest.cpp create mode 100644 fnet/src/vespa/fnet/frt/rpcrequest.h create mode 100644 fnet/src/vespa/fnet/frt/supervisor.cpp create mode 100644 fnet/src/vespa/fnet/frt/supervisor.h create mode 100644 fnet/src/vespa/fnet/frt/target.cpp create mode 100644 fnet/src/vespa/fnet/frt/target.h create mode 100644 fnet/src/vespa/fnet/frt/values.cpp create mode 100644 fnet/src/vespa/fnet/frt/values.h create mode 100644 fnet/src/vespa/fnet/iexecutable.h create mode 100644 fnet/src/vespa/fnet/info.cpp create mode 100644 fnet/src/vespa/fnet/info.h create mode 100644 fnet/src/vespa/fnet/iocomponent.cpp create mode 100644 fnet/src/vespa/fnet/iocomponent.h create mode 100644 fnet/src/vespa/fnet/ipacketfactory.h create mode 100644 fnet/src/vespa/fnet/ipackethandler.h create mode 100644 fnet/src/vespa/fnet/ipacketstreamer.h create mode 100644 fnet/src/vespa/fnet/iserveradapter.h create mode 100644 fnet/src/vespa/fnet/packet.cpp create mode 100644 fnet/src/vespa/fnet/packet.h create mode 100644 fnet/src/vespa/fnet/packetqueue.cpp create mode 100644 fnet/src/vespa/fnet/packetqueue.h create mode 100644 fnet/src/vespa/fnet/scheduler.cpp create mode 100644 fnet/src/vespa/fnet/scheduler.h create mode 100644 fnet/src/vespa/fnet/signalshutdown.cpp create mode 100644 fnet/src/vespa/fnet/signalshutdown.h create mode 100644 fnet/src/vespa/fnet/simplepacketstreamer.cpp create mode 100644 fnet/src/vespa/fnet/simplepacketstreamer.h create mode 100644 fnet/src/vespa/fnet/spiral/.gitignore create mode 100644 fnet/src/vespa/fnet/stats.cpp create mode 100644 fnet/src/vespa/fnet/stats.h create mode 100644 fnet/src/vespa/fnet/task.cpp create mode 100644 fnet/src/vespa/fnet/task.h create mode 100644 fnet/src/vespa/fnet/testkit/xsync/.gitignore create mode 100644 fnet/src/vespa/fnet/transport.cpp create mode 100644 fnet/src/vespa/fnet/transport.h create mode 100644 fnet/src/vespa/fnet/transport_thread.cpp create mode 100644 fnet/src/vespa/fnet/transport_thread.h create mode 100644 fnet/src/vespa/fnet/vtag.cpp create mode 100644 fnet/src/vespa/fnet/vtag.h create mode 100644 fnet/testrun/.gitignore (limited to 'fnet') 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% [] + (run ./configure --help for help) +- make install + +Install fnet: +- checkout the fnet CVS module +- go to fnet/src +- ./configure --fastos-dir %FASTOS_DIR% [] + (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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +#include +#include +#include + +/* 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 @@ + + + FNET Home Page + +

FNET Home Page

+ + + 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 , where +# is the value of the INPUT_FILTER tag, and 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 +#include +LOG_SETUP("echo_client"); +#include + +class EchoClient : public FastOS_Application +{ +public: + virtual int Main(); +}; + +int +EchoClient::Main() +{ + if (_argc < 2) { + printf("usage : echo_client \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 +#include +LOG_SETUP("rpc_callback_client"); +#include + + +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 \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 +#include +LOG_SETUP("rpc_callback_server"); +#include + + +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 \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 +#include +LOG_SETUP("rpc_client"); +#include + + +class RPCClient : public FastOS_Application +{ +public: + virtual int Main(); +}; + +int +RPCClient::Main() +{ + if (_argc < 2) { + printf("usage : rpc_client \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 +#include +LOG_SETUP("rpc_info"); +#include + +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 [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 +#include +LOG_SETUP("rpc_invoke"); +#include + +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] [args]\n"); + fprintf(stderr, " -t timeout in seconds\n"); + fprintf(stderr, " Each arg must be on the form :\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 +#include +LOG_SETUP("rpc_proxy"); +#include + +//----------------------------------------------------------------------------- + +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 [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 +#include +LOG_SETUP("rpc_server"); +#include + + +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 ¶ms = *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 ¶ms = *req->GetParams(); + FRT_Values &ret = *req->GetReturn(); + + ret.AddFloat(params[0]._float + params[1]._float); +} + +void +RPCServer::RPC_addDouble(FRT_RPCRequest *req) +{ + FRT_Values ¶ms = *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 \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 +#include +#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 +#include +LOG_SETUP("pingclient"); +#include +#include + + +class PingClient : public FastOS_Application +{ +public: + int Main(); +}; + + +int +PingClient::Main() +{ + if (_argc < 2) { + printf("usage : pingclient \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 +#include +LOG_SETUP("pingserver"); +#include +#include + + +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 \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 +#include +LOG_SETUP("proxy"); +#include + + +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 \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 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 +#include +LOG_SETUP("timeout"); +#include + + +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 +#include + +struct MyConn : public fnet::ExtConnectable { + bool connected = false; + void ext_connect() override { connected = true; } +}; + +TEST("require that connect thread will connect stuff") { + std::vector 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 +#include +#include +#include +#include +#include + +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 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 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 +#include + +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 +#include +#include +#include + +// 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 +#include + + +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 +#include + +//--------------------------------------------------------------- + +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 +#include +#include + +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 +#include +#include +#include +#include + +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 ¶ms = *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 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 +#include +#include + +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 +#include + +//------------------------------------------------------------- + +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 ¶m = *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 +#include + + +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 +#include +#include + +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 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 +#include + +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 +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 +#include +#include +#include + +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 +#include + +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(myB); + } + B* TypesafeCast() { + return myB->asB(); + } + B* UnsafeCast() { + return reinterpret_cast(myB); + } + B* StaticCast() { + return static_cast(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 +#include + + +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 +#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 +#include +#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 +#include + +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 diff --git a/fnet/src/tests/regress/fdselector/.gitignore b/fnet/src/tests/regress/fdselector/.gitignore new file mode 100644 index 00000000000..e69de29bb2d 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 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 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 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 diff --git a/fnet/src/tests/regress/info/.gitignore b/fnet/src/tests/regress/info/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/fnet/src/tests/regress/locking/.gitignore b/fnet/src/tests/regress/locking/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/fnet/src/tests/regress/scheduling/.gitignore b/fnet/src/tests/regress/scheduling/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/fnet/src/tests/regress/spiral/.gitignore b/fnet/src/tests/regress/spiral/.gitignore new file mode 100644 index 00000000000..e69de29bb2d 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 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 diff --git a/fnet/src/tests/regress/time/.gitignore b/fnet/src/tests/regress/time/.gitignore new file mode 100644 index 00000000000..e69de29bb2d 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 +#include + +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 +#include + + +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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +struct Fixture { + std::mutex lock; + FNET_Transport transport; + std::map counts; + Fixture(size_t num_threads) : transport(num_threads) {} + void count_selected_thread(const void *key, size_t key_len) { + std::lock_guard guard(lock); + FNET_TransportThread *thread = transport.select_thread(key, key_len); + ++counts[thread]; + } + std::vector get_counts() { + std::vector result; + for (const auto &entry: counts) { + result.push_back(entry.second); + } + return result; + } + void dump_counts() { + std::vector 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 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 +#include + + +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 + $ + 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 +#include + +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 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 +#include + + +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_ChannelLookup::Broadcast(FNET_ControlPacket *cpacket) +{ + std::vector toRemove; + std::vector 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 + +/** + * 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 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 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 +#include + + +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 +#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 +#include +#include +#include + +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 _lock; + std::condition_variable _cond; + vespalib::ArrayQueue _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 +#include +LOG_SETUP(".fnet"); +#include + +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 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 +#include +LOG_SETUP(".fnet"); +#include + + +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 +#include +#include + +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 +#include + +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(_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(_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(_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 +#include + +/** + * 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 +#include +#include + + +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 +#include +LOG_SETUP(".fnet"); +#include + + +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 + +// 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 +#include + +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 +#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 +#include +LOG_SETUP(".fnet.frt.invoker"); +#include + + +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 +#include + +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 + +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(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 +#include +#include + + +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 +#include + + +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 +#include + +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 + + +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 +#include + + +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 +#include + +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 + +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 +#include + + +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 +struct FRT_Array { + uint32_t _len; + T *_pt; +}; + +#define FRT_LPT(type) FRT_Array + +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(_data.get()); } + char *getInternalData() { return static_cast(_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(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 +#include +LOG_SETUP(".fnet"); +#include + + +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 +#include + + +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 +#include +#include + + +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 + +/** + * 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 UP; + typedef std::shared_ptr 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 +#include + + +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 +#include +LOG_SETUP(".fnet.scheduler"); +#include +#include +#include + +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 +#include +#include + +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 +#include + + +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 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 +#include +LOG_SETUP(".fnet"); +#include + +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 +#include + + +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 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 +#include +LOG_SETUP(".fnet"); +#include +#include +#include + +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 + +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; + using Threads = std::vector; + + 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 +#include +LOG_SETUP(".fnet"); +#include +#include + +namespace { + +struct Sync : public FNET_IExecutable +{ + vespalib::Gate gate; + virtual void execute() { + gate.countDown(); + } +}; + +} // namespace + + +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 +#include + +#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 -- cgit v1.2.3