gorealis v2 refactor (#5)

* Changing default timeout for start maintenance.

* Upgrading dependencies to gorealis v2 and thrift  0.12.0

* Refactored to update to gorealis v2.
This commit is contained in:
Renan DelValle 2018-12-27 11:31:51 -08:00 committed by GitHub
parent ad4dd9606e
commit 6ab5c9334d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1335 changed files with 137431 additions and 61530 deletions

View file

@ -17,39 +17,52 @@
* under the License.
*/
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/async/TAsyncBufferProcessor.h>
#include <thrift/async/TAsyncProtocolProcessor.h>
#include <thrift/async/TEvhttpServer.h>
#include <thrift/concurrency/PlatformThreadFactory.h>
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/processor/TMultiplexedProcessor.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/protocol/TCompactProtocol.h>
#include <thrift/protocol/THeaderProtocol.h>
#include <thrift/protocol/TJSONProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/async/TEvhttpServer.h>
#include <thrift/async/TAsyncBufferProcessor.h>
#include <thrift/async/TAsyncProtocolProcessor.h>
#include <thrift/server/TNonblockingServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TSSLServerSocket.h>
#include <thrift/transport/TSSLSocket.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/THttpServer.h>
#include <thrift/transport/THttpTransport.h>
#include <thrift/transport/TNonblockingSSLServerSocket.h>
#include <thrift/transport/TNonblockingServerSocket.h>
#include <thrift/transport/TSSLServerSocket.h>
#include <thrift/transport/TSSLSocket.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/transport/TZlibTransport.h>
#include "SecondService.h"
#include "ThriftTest.h"
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <thrift/cxxfunctional.h>
#include <thrift/stdcxx.h>
#include <signal.h>
#if _WIN32
#include <thrift/windows/TWinsockSingleton.h>
#endif
@ -57,14 +70,25 @@
using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::async;
using namespace apache::thrift::concurrency;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using namespace apache::thrift::server;
using namespace apache::thrift::async;
using namespace thrift::test;
// to handle a controlled shutdown, signal handling is mandatory
#ifdef HAVE_SIGNAL_H
apache::thrift::concurrency::Monitor gMonitor;
void signal_handler(int signum)
{
if (signum == SIGINT) {
gMonitor.notifyAll();
}
}
#endif
class TestHandler : public ThriftTestIf {
public:
TestHandler() {}
@ -104,7 +128,7 @@ public:
void testBinary(std::string& _return, const std::string& thing) {
std::ostringstream hexstr;
hexstr << std::hex << thing;
printf("testBinary(%s)\n", hexstr.str().c_str());
printf("testBinary(%lu: %s)\n", safe_numeric_cast<unsigned long>(thing.size()), hexstr.str().c_str());
_return = thing;
}
@ -326,13 +350,18 @@ public:
}
}
void testOneway(const int32_t sleepFor) {
printf("testOneway(%d): Sleeping...\n", sleepFor);
THRIFT_SLEEP_SEC(sleepFor);
printf("testOneway(%d): done sleeping!\n", sleepFor);
void testOneway(const int32_t aNum) {
printf("testOneway(%d): call received\n", aNum);
}
};
class SecondHandler : public SecondServiceIf
{
public:
void secondtestString(std::string& result, const std::string& thing)
{ result = "testString(\"" + thing + "\")"; }
};
class TestProcessorEventHandler : public TProcessorEventHandler {
virtual void* getContext(const char* fn_name, void* serverContext) {
(void)serverContext;
@ -366,66 +395,66 @@ class TestProcessorEventHandler : public TProcessorEventHandler {
class TestHandlerAsync : public ThriftTestCobSvIf {
public:
TestHandlerAsync(boost::shared_ptr<TestHandler>& handler) : _delegate(handler) {}
TestHandlerAsync(stdcxx::shared_ptr<TestHandler>& handler) : _delegate(handler) {}
virtual ~TestHandlerAsync() {}
virtual void testVoid(tcxx::function<void()> cob) {
virtual void testVoid(stdcxx::function<void()> cob) {
_delegate->testVoid();
cob();
}
virtual void testString(tcxx::function<void(std::string const& _return)> cob,
virtual void testString(stdcxx::function<void(std::string const& _return)> cob,
const std::string& thing) {
std::string res;
_delegate->testString(res, thing);
cob(res);
}
virtual void testBool(tcxx::function<void(bool const& _return)> cob, const bool thing) {
virtual void testBool(stdcxx::function<void(bool const& _return)> cob, const bool thing) {
bool res = _delegate->testBool(thing);
cob(res);
}
virtual void testByte(tcxx::function<void(int8_t const& _return)> cob, const int8_t thing) {
virtual void testByte(stdcxx::function<void(int8_t const& _return)> cob, const int8_t thing) {
int8_t res = _delegate->testByte(thing);
cob(res);
}
virtual void testI32(tcxx::function<void(int32_t const& _return)> cob, const int32_t thing) {
virtual void testI32(stdcxx::function<void(int32_t const& _return)> cob, const int32_t thing) {
int32_t res = _delegate->testI32(thing);
cob(res);
}
virtual void testI64(tcxx::function<void(int64_t const& _return)> cob, const int64_t thing) {
virtual void testI64(stdcxx::function<void(int64_t const& _return)> cob, const int64_t thing) {
int64_t res = _delegate->testI64(thing);
cob(res);
}
virtual void testDouble(tcxx::function<void(double const& _return)> cob, const double thing) {
virtual void testDouble(stdcxx::function<void(double const& _return)> cob, const double thing) {
double res = _delegate->testDouble(thing);
cob(res);
}
virtual void testBinary(tcxx::function<void(std::string const& _return)> cob,
virtual void testBinary(stdcxx::function<void(std::string const& _return)> cob,
const std::string& thing) {
std::string res;
_delegate->testBinary(res, thing);
cob(res);
}
virtual void testStruct(tcxx::function<void(Xtruct const& _return)> cob, const Xtruct& thing) {
virtual void testStruct(stdcxx::function<void(Xtruct const& _return)> cob, const Xtruct& thing) {
Xtruct res;
_delegate->testStruct(res, thing);
cob(res);
}
virtual void testNest(tcxx::function<void(Xtruct2 const& _return)> cob, const Xtruct2& thing) {
virtual void testNest(stdcxx::function<void(Xtruct2 const& _return)> cob, const Xtruct2& thing) {
Xtruct2 res;
_delegate->testNest(res, thing);
cob(res);
}
virtual void testMap(tcxx::function<void(std::map<int32_t, int32_t> const& _return)> cob,
virtual void testMap(stdcxx::function<void(std::map<int32_t, int32_t> const& _return)> cob,
const std::map<int32_t, int32_t>& thing) {
std::map<int32_t, int32_t> res;
_delegate->testMap(res, thing);
@ -433,40 +462,40 @@ public:
}
virtual void testStringMap(
tcxx::function<void(std::map<std::string, std::string> const& _return)> cob,
stdcxx::function<void(std::map<std::string, std::string> const& _return)> cob,
const std::map<std::string, std::string>& thing) {
std::map<std::string, std::string> res;
_delegate->testStringMap(res, thing);
cob(res);
}
virtual void testSet(tcxx::function<void(std::set<int32_t> const& _return)> cob,
virtual void testSet(stdcxx::function<void(std::set<int32_t> const& _return)> cob,
const std::set<int32_t>& thing) {
std::set<int32_t> res;
_delegate->testSet(res, thing);
cob(res);
}
virtual void testList(tcxx::function<void(std::vector<int32_t> const& _return)> cob,
virtual void testList(stdcxx::function<void(std::vector<int32_t> const& _return)> cob,
const std::vector<int32_t>& thing) {
std::vector<int32_t> res;
_delegate->testList(res, thing);
cob(res);
}
virtual void testEnum(tcxx::function<void(Numberz::type const& _return)> cob,
virtual void testEnum(stdcxx::function<void(Numberz::type const& _return)> cob,
const Numberz::type thing) {
Numberz::type res = _delegate->testEnum(thing);
cob(res);
}
virtual void testTypedef(tcxx::function<void(UserId const& _return)> cob, const UserId thing) {
virtual void testTypedef(stdcxx::function<void(UserId const& _return)> cob, const UserId thing) {
UserId res = _delegate->testTypedef(thing);
cob(res);
}
virtual void testMapMap(
tcxx::function<void(std::map<int32_t, std::map<int32_t, int32_t> > const& _return)> cob,
stdcxx::function<void(std::map<int32_t, std::map<int32_t, int32_t> > const& _return)> cob,
const int32_t hello) {
std::map<int32_t, std::map<int32_t, int32_t> > res;
_delegate->testMapMap(res, hello);
@ -474,14 +503,14 @@ public:
}
virtual void testInsanity(
tcxx::function<void(std::map<UserId, std::map<Numberz::type, Insanity> > const& _return)> cob,
stdcxx::function<void(std::map<UserId, std::map<Numberz::type, Insanity> > const& _return)> cob,
const Insanity& argument) {
std::map<UserId, std::map<Numberz::type, Insanity> > res;
_delegate->testInsanity(res, argument);
cob(res);
}
virtual void testMulti(tcxx::function<void(Xtruct const& _return)> cob,
virtual void testMulti(stdcxx::function<void(Xtruct const& _return)> cob,
const int8_t arg0,
const int32_t arg1,
const int64_t arg2,
@ -494,8 +523,8 @@ public:
}
virtual void testException(
tcxx::function<void()> cob,
tcxx::function<void(::apache::thrift::TDelayedException* _throw)> exn_cob,
stdcxx::function<void()> cob,
stdcxx::function<void(::apache::thrift::TDelayedException* _throw)> exn_cob,
const std::string& arg) {
try {
_delegate->testException(arg);
@ -507,8 +536,8 @@ public:
}
virtual void testMultiException(
tcxx::function<void(Xtruct const& _return)> cob,
tcxx::function<void(::apache::thrift::TDelayedException* _throw)> exn_cob,
stdcxx::function<void(Xtruct const& _return)> cob,
stdcxx::function<void(::apache::thrift::TDelayedException* _throw)> exn_cob,
const std::string& arg0,
const std::string& arg1) {
Xtruct res;
@ -521,13 +550,13 @@ public:
cob(res);
}
virtual void testOneway(tcxx::function<void()> cob, const int32_t secondsToSleep) {
virtual void testOneway(stdcxx::function<void()> cob, const int32_t secondsToSleep) {
_delegate->testOneway(secondsToSleep);
cob();
}
protected:
boost::shared_ptr<TestHandler> _delegate;
stdcxx::shared_ptr<TestHandler> _delegate;
};
namespace po = boost::program_options;
@ -543,6 +572,7 @@ int main(int argc, char** argv) {
#endif
int port = 9090;
bool ssl = false;
bool zlib = false;
string transport_type = "buffered";
string protocol_type = "binary";
string server_type = "simple";
@ -559,9 +589,10 @@ int main(int argc, char** argv) {
("domain-socket", po::value<string>(&domain_socket) ->default_value(domain_socket), "Unix Domain Socket (e.g. /tmp/ThriftTest.thrift)")
("abstract-namespace", "Create the domain socket in the Abstract Namespace (no connection with filesystem pathnames)")
("server-type", po::value<string>(&server_type)->default_value(server_type), "type of server, \"simple\", \"thread-pool\", \"threaded\", or \"nonblocking\"")
("transport", po::value<string>(&transport_type)->default_value(transport_type), "transport: buffered, framed, http")
("protocol", po::value<string>(&protocol_type)->default_value(protocol_type), "protocol: binary, compact, header, json")
("transport", po::value<string>(&transport_type)->default_value(transport_type), "transport: buffered, framed, http, zlib")
("protocol", po::value<string>(&protocol_type)->default_value(protocol_type), "protocol: binary, compact, header, json, multi, multic, multih, multij")
("ssl", "Encrypted Transport using SSL")
("zlib", "Wrapped Transport using Zlib")
("processor-events", "processor-events")
("workers,n", po::value<size_t>(&workers)->default_value(workers), "Number of thread pools workers. Only valid for thread-pool server type")
("string-limit", po::value<int>(&string_limit))
@ -592,6 +623,10 @@ int main(int argc, char** argv) {
} else if (protocol_type == "compact") {
} else if (protocol_type == "json") {
} else if (protocol_type == "header") {
} else if (protocol_type == "multi") { // multiplexed binary
} else if (protocol_type == "multic") { // multiplexed compact
} else if (protocol_type == "multih") { // multiplexed header
} else if (protocol_type == "multij") { // multiplexed json
} else {
throw invalid_argument("Unknown protocol type " + protocol_type);
}
@ -601,6 +636,8 @@ int main(int argc, char** argv) {
if (transport_type == "buffered") {
} else if (transport_type == "framed") {
} else if (transport_type == "http") {
} else if (transport_type == "zlib") {
// crosstester will pass zlib as a flag and a transport right now...
} else {
throw invalid_argument("Unknown transport type " + transport_type);
}
@ -616,22 +653,32 @@ int main(int argc, char** argv) {
ssl = true;
}
if (vm.count("zlib")) {
zlib = true;
}
#if defined(HAVE_SIGNAL_H) && defined(SIGPIPE)
if (ssl) {
signal(SIGPIPE, SIG_IGN); // for OpenSSL, otherwise we end abruptly
}
#endif
if (vm.count("abstract-namespace")) {
abstract_namespace = true;
}
// Dispatcher
boost::shared_ptr<TProtocolFactory> protocolFactory;
if (protocol_type == "json") {
boost::shared_ptr<TProtocolFactory> jsonProtocolFactory(new TJSONProtocolFactory());
stdcxx::shared_ptr<TProtocolFactory> protocolFactory;
if (protocol_type == "json" || protocol_type == "multij") {
stdcxx::shared_ptr<TProtocolFactory> jsonProtocolFactory(new TJSONProtocolFactory());
protocolFactory = jsonProtocolFactory;
} else if (protocol_type == "compact") {
} else if (protocol_type == "compact" || protocol_type == "multic") {
TCompactProtocolFactoryT<TBufferBase> *compactProtocolFactory = new TCompactProtocolFactoryT<TBufferBase>();
compactProtocolFactory->setContainerSizeLimit(container_limit);
compactProtocolFactory->setStringSizeLimit(string_limit);
protocolFactory.reset(compactProtocolFactory);
} else if (protocol_type == "header") {
boost::shared_ptr<TProtocolFactory> headerProtocolFactory(new THeaderProtocolFactory());
} else if (protocol_type == "header" || protocol_type == "multih") {
stdcxx::shared_ptr<TProtocolFactory> headerProtocolFactory(new THeaderProtocolFactory());
protocolFactory = headerProtocolFactory;
} else {
TBinaryProtocolFactoryT<TBufferBase>* binaryProtocolFactory = new TBinaryProtocolFactoryT<TBufferBase>();
@ -640,53 +687,57 @@ int main(int argc, char** argv) {
protocolFactory.reset(binaryProtocolFactory);
}
// Processor
boost::shared_ptr<TestHandler> testHandler(new TestHandler());
boost::shared_ptr<ThriftTestProcessor> testProcessor(new ThriftTestProcessor(testHandler));
// Processors
stdcxx::shared_ptr<TestHandler> testHandler(new TestHandler());
stdcxx::shared_ptr<TProcessor> testProcessor(new ThriftTestProcessor(testHandler));
if (vm.count("processor-events")) {
testProcessor->setEventHandler(
boost::shared_ptr<TProcessorEventHandler>(new TestProcessorEventHandler()));
stdcxx::shared_ptr<TProcessorEventHandler>(new TestProcessorEventHandler()));
}
// Transport
boost::shared_ptr<TSSLSocketFactory> sslSocketFactory;
boost::shared_ptr<TServerSocket> serverSocket;
stdcxx::shared_ptr<TSSLSocketFactory> sslSocketFactory;
stdcxx::shared_ptr<TServerSocket> serverSocket;
if (ssl) {
sslSocketFactory = boost::shared_ptr<TSSLSocketFactory>(new TSSLSocketFactory());
sslSocketFactory = stdcxx::shared_ptr<TSSLSocketFactory>(new TSSLSocketFactory());
sslSocketFactory->loadCertificate(certPath.c_str());
sslSocketFactory->loadPrivateKey(keyPath.c_str());
sslSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
serverSocket = boost::shared_ptr<TServerSocket>(new TSSLServerSocket(port, sslSocketFactory));
if (server_type != "nonblocking") {
serverSocket = stdcxx::shared_ptr<TServerSocket>(new TSSLServerSocket(port, sslSocketFactory));
}
} else {
if (domain_socket != "") {
if (abstract_namespace) {
std::string abstract_socket("\0", 1);
abstract_socket += domain_socket;
serverSocket = boost::shared_ptr<TServerSocket>(new TServerSocket(abstract_socket));
serverSocket = stdcxx::shared_ptr<TServerSocket>(new TServerSocket(abstract_socket));
} else {
unlink(domain_socket.c_str());
serverSocket = boost::shared_ptr<TServerSocket>(new TServerSocket(domain_socket));
serverSocket = stdcxx::shared_ptr<TServerSocket>(new TServerSocket(domain_socket));
}
port = 0;
} else {
serverSocket = boost::shared_ptr<TServerSocket>(new TServerSocket(port));
serverSocket = stdcxx::shared_ptr<TServerSocket>(new TServerSocket(port));
}
}
// Factory
boost::shared_ptr<TTransportFactory> transportFactory;
stdcxx::shared_ptr<TTransportFactory> transportFactory;
if (transport_type == "http" && server_type != "nonblocking") {
boost::shared_ptr<TTransportFactory> httpTransportFactory(new THttpServerTransportFactory());
transportFactory = httpTransportFactory;
transportFactory = stdcxx::make_shared<THttpServerTransportFactory>();
} else if (transport_type == "framed") {
boost::shared_ptr<TTransportFactory> framedTransportFactory(new TFramedTransportFactory());
transportFactory = framedTransportFactory;
transportFactory = stdcxx::make_shared<TFramedTransportFactory>();
} else {
boost::shared_ptr<TTransportFactory> bufferedTransportFactory(new TBufferedTransportFactory());
transportFactory = bufferedTransportFactory;
transportFactory = stdcxx::make_shared<TBufferedTransportFactory>();
}
if (zlib) {
// hmm.. doesn't seem to be a way to make it wrap the others...
transportFactory = stdcxx::make_shared<TZlibTransportFactory>();
}
// Server Info
@ -701,20 +752,30 @@ int main(int argc, char** argv) {
}
cout << endl;
// Multiplexed Processor if needed
if (boost::starts_with(protocol_type, "multi")) {
stdcxx::shared_ptr<SecondHandler> secondHandler(new SecondHandler());
stdcxx::shared_ptr<SecondServiceProcessor> secondProcessor(new SecondServiceProcessor(secondHandler));
stdcxx::shared_ptr<TMultiplexedProcessor> multiplexedProcessor(new TMultiplexedProcessor());
multiplexedProcessor->registerDefault(testProcessor); // non-multi clients go to the default processor (multi:binary, multic:compact, ...)
multiplexedProcessor->registerProcessor("ThriftTest", testProcessor);
multiplexedProcessor->registerProcessor("SecondService", secondProcessor);
testProcessor = stdcxx::dynamic_pointer_cast<TProcessor>(multiplexedProcessor);
}
// Server
boost::shared_ptr<apache::thrift::server::TServer> server;
stdcxx::shared_ptr<apache::thrift::server::TServer> server;
if (server_type == "simple") {
server.reset(new TSimpleServer(testProcessor, serverSocket, transportFactory, protocolFactory));
} else if (server_type == "thread-pool") {
boost::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(workers);
boost::shared_ptr<PlatformThreadFactory> threadFactory
= boost::shared_ptr<PlatformThreadFactory>(new PlatformThreadFactory());
stdcxx::shared_ptr<PlatformThreadFactory> threadFactory
= stdcxx::shared_ptr<PlatformThreadFactory>(new PlatformThreadFactory());
stdcxx::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(workers);
threadManager->threadFactory(threadFactory);
threadManager->start();
server.reset(new TThreadPoolServer(testProcessor,
@ -723,15 +784,14 @@ int main(int argc, char** argv) {
protocolFactory,
threadManager));
} else if (server_type == "threaded") {
server.reset(
new TThreadedServer(testProcessor, serverSocket, transportFactory, protocolFactory));
} else if (server_type == "nonblocking") {
if (transport_type == "http") {
boost::shared_ptr<TestHandlerAsync> testHandlerAsync(new TestHandlerAsync(testHandler));
boost::shared_ptr<TAsyncProcessor> testProcessorAsync(
stdcxx::shared_ptr<TestHandlerAsync> testHandlerAsync(new TestHandlerAsync(testHandler));
stdcxx::shared_ptr<TAsyncProcessor> testProcessorAsync(
new ThriftTestAsyncProcessor(testHandlerAsync));
boost::shared_ptr<TAsyncBufferProcessor> testBufferProcessor(
stdcxx::shared_ptr<TAsyncBufferProcessor> testBufferProcessor(
new TAsyncProtocolProcessor(testProcessorAsync, protocolFactory));
// not loading nonblockingServer into "server" because
@ -739,8 +799,15 @@ int main(int argc, char** argv) {
// provide a stop method.
TEvhttpServer nonblockingServer(testBufferProcessor, port);
nonblockingServer.serve();
} else if (transport_type == "framed") {
stdcxx::shared_ptr<transport::TNonblockingServerTransport> nbSocket;
nbSocket.reset(
ssl ? new transport::TNonblockingSSLServerSocket(port, sslSocketFactory)
: new transport::TNonblockingServerSocket(port));
server.reset(new TNonblockingServer(testProcessor, protocolFactory, nbSocket));
} else {
server.reset(new TNonblockingServer(testProcessor, protocolFactory, port));
cerr << "server-type nonblocking requires transport of http or framed" << endl;
exit(1);
}
}
@ -748,22 +815,25 @@ int main(int argc, char** argv) {
if (protocol_type == "header") {
// Tell the server to use the same protocol for input / output
// if using header
server->setOutputProtocolFactory(boost::shared_ptr<TProtocolFactory>());
server->setOutputProtocolFactory(stdcxx::shared_ptr<TProtocolFactory>());
}
apache::thrift::concurrency::PlatformThreadFactory factory;
factory.setDetached(false);
boost::shared_ptr<apache::thrift::concurrency::Runnable> serverThreadRunner(server);
boost::shared_ptr<apache::thrift::concurrency::Thread> thread
stdcxx::shared_ptr<apache::thrift::concurrency::Runnable> serverThreadRunner(server);
stdcxx::shared_ptr<apache::thrift::concurrency::Thread> thread
= factory.newThread(serverThreadRunner);
thread->start();
// HACK: cross language test suite is unable to handle cin properly
// that's why we stay in a endless loop here
while (1) {
}
// FIXME: find another way to stop the server (e.g. a signal)
// cout<<"Press enter to stop the server."<<endl;
// cin.ignore(); //wait until a key is pressed
#ifdef HAVE_SIGNAL_H
signal(SIGINT, signal_handler);
#endif
thread->start();
gMonitor.waitForever(); // wait for a shutdown signal
#ifdef HAVE_SIGNAL_H
signal(SIGINT, SIG_DFL);
#endif
server->stop();
thread->join();
@ -773,3 +843,4 @@ int main(int argc, char** argv) {
cout << "done." << endl;
return 0;
}