blob: b35a63bd67dcb1b0c855a1a6bdd119b070da3426 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file rpc_server.cc
* \brief RPC Server implementation.
*/
#include <tvm/runtime/registry.h>
#if defined(__linux__) || defined(__ANDROID__)
#include <sys/select.h>
#include <sys/wait.h>
#endif
#include <set>
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
#include <string>
#include "rpc_server.h"
#include "rpc_env.h"
#include "rpc_tracker_client.h"
#include "../../src/runtime/rpc/rpc_session.h"
#include "../../src/runtime/rpc/rpc_socket_impl.h"
#include "../../src/common/socket.h"
namespace tvm {
namespace runtime {
/*!
* \brief wait the child process end.
* \param status status value
*/
#if defined(__linux__) || defined(__ANDROID__)
static pid_t waitPidEintr(int *status) {
pid_t pid = 0;
while ((pid = waitpid(-1, status, 0)) == -1) {
if (errno == EINTR) {
continue;
} else {
perror("waitpid");
abort();
}
}
return pid;
}
#endif
/*!
* \brief RPCServer RPC Server class.
* \param host The hostname of the server, Default=0.0.0.0
* \param port The port of the RPC, Default=9090
* \param port_end The end search port of the RPC, Default=9199
* \param tracker The address of RPC tracker in host:port format e.g. 10.77.1.234:9190 Default=""
* \param key The key used to identify the device type in tracker. Default=""
* \param custom_addr Custom IP Address to Report to RPC Tracker. Default=""
*/
class RPCServer {
public:
/*!
* \brief Constructor.
*/
RPCServer(const std::string &host,
int port,
int port_end,
const std::string &tracker_addr,
const std::string &key,
const std::string &custom_addr) {
// Init the values
host_ = host;
port_ = port;
port_end_ = port_end;
tracker_addr_ = tracker_addr;
key_ = key;
custom_addr_ = custom_addr;
}
/*!
* \brief Destructor.
*/
~RPCServer() {
// Free the resources
tracker_sock_.Close();
listen_sock_.Close();
}
/*!
* \brief Start Creates the RPC listen process and execution.
*/
void Start() {
listen_sock_.Create();
my_port_ = listen_sock_.TryBindHost(host_, port_, port_end_);
LOG(INFO) << "bind to " << host_ << ":" << my_port_;
listen_sock_.Listen(1);
std::future<void> proc(std::async(std::launch::async, &RPCServer::ListenLoopProc, this));
proc.get();
// Close the listen socket
listen_sock_.Close();
}
private:
/*!
* \brief ListenLoopProc The listen process.
*/
void ListenLoopProc() {
TrackerClient tracker(tracker_addr_, key_, custom_addr_);
while (true) {
common::TCPSocket conn;
common::SockAddr addr("0.0.0.0", 0);
std::string opts;
try {
// step 1: setup tracker and report to tracker
tracker.TryConnect();
// step 2: wait for in-coming connections
AcceptConnection(&tracker, &conn, &addr, &opts);
}
catch (const char* msg) {
LOG(WARNING) << "Socket exception: " << msg;
// close tracker resource
tracker.Close();
continue;
}
catch (std::exception& e) {
// Other errors
LOG(WARNING) << "Exception standard: " << e.what();
continue;
}
int timeout = GetTimeOutFromOpts(opts);
#if defined(__linux__) || defined(__ANDROID__)
// step 3: serving
if (timeout != 0) {
const pid_t timer_pid = fork();
if (timer_pid == 0) {
// Timer process
sleep(timeout);
exit(0);
}
const pid_t worker_pid = fork();
if (worker_pid == 0) {
// Worker process
ServerLoopProc(conn, addr);
exit(0);
}
int status = 0;
const pid_t finished_first = waitPidEintr(&status);
if (finished_first == timer_pid) {
kill(worker_pid, SIGKILL);
} else if (finished_first == worker_pid) {
kill(timer_pid, SIGKILL);
} else {
LOG(INFO) << "Child pid=" << finished_first << " unexpected, but still continue.";
}
int status_second = 0;
waitPidEintr(&status_second);
// Logging.
if (finished_first == timer_pid) {
LOG(INFO) << "Child pid=" << worker_pid << " killed (timeout = " << timeout
<< "), Process status = " << status_second;
} else if (finished_first == worker_pid) {
LOG(INFO) << "Child pid=" << timer_pid << " killed, Process status = " << status_second;
}
} else {
auto pid = fork();
if (pid == 0) {
ServerLoopProc(conn, addr);
exit(0);
}
// Wait for the result
int status = 0;
wait(&status);
LOG(INFO) << "Child pid=" << pid << " exited, Process status =" << status;
}
#else
// step 3: serving
std::future<void> proc(std::async(std::launch::async,
&RPCServer::ServerLoopProc, this, conn, addr));
// wait until server process finish or timeout
if (timeout != 0) {
// Autoterminate after timeout
proc.wait_for(std::chrono::seconds(timeout));
} else {
// Wait for the result
proc.get();
}
#endif
// close from our side.
LOG(INFO) << "Socket Connection Closed";
conn.Close();
}
}
/*!
* \brief AcceptConnection Accepts the RPC Server connection.
* \param tracker Tracker details.
* \param conn New connection information.
* \param addr New connection address information.
* \param opts Parsed options for socket
* \param ping_period Timeout for select call waiting
*/
void AcceptConnection(TrackerClient* tracker,
common::TCPSocket* conn_sock,
common::SockAddr* addr,
std::string* opts,
int ping_period = 2) {
std::set <std::string> old_keyset;
std::string matchkey;
// Report resource to tracker and get key
tracker->ReportResourceAndGetKey(my_port_, &matchkey);
while (true) {
tracker->WaitConnectionAndUpdateKey(listen_sock_, my_port_, ping_period, &matchkey);
common::TCPSocket conn = listen_sock_.Accept(addr);
int code = kRPCMagic;
CHECK_EQ(conn.RecvAll(&code, sizeof(code)), sizeof(code));
if (code != kRPCMagic) {
conn.Close();
LOG(FATAL) << "Client connected is not TVM RPC server";
continue;
}
int keylen = 0;
CHECK_EQ(conn.RecvAll(&keylen, sizeof(keylen)), sizeof(keylen));
const char* CLIENT_HEADER = "client:";
const char* SERVER_HEADER = "server:";
std::string expect_header = CLIENT_HEADER + matchkey;
std::string server_key = SERVER_HEADER + key_;
if (size_t(keylen) < expect_header.length()) {
conn.Close();
LOG(INFO) << "Wrong client header length";
continue;
}
CHECK_NE(keylen, 0);
std::string remote_key;
remote_key.resize(keylen);
CHECK_EQ(conn.RecvAll(&remote_key[0], keylen), keylen);
std::stringstream ssin(remote_key);
std::string arg0;
ssin >> arg0;
if (arg0 != expect_header) {
code = kRPCMismatch;
CHECK_EQ(conn.SendAll(&code, sizeof(code)), sizeof(code));
conn.Close();
LOG(WARNING) << "Mismatch key from" << addr->AsString();
continue;
} else {
code = kRPCSuccess;
CHECK_EQ(conn.SendAll(&code, sizeof(code)), sizeof(code));
keylen = server_key.length();
CHECK_EQ(conn.SendAll(&keylen, sizeof(keylen)), sizeof(keylen));
CHECK_EQ(conn.SendAll(server_key.c_str(), keylen), keylen);
LOG(INFO) << "Connection success " << addr->AsString();
ssin >> *opts;
*conn_sock = conn;
return;
}
}
}
/*!
* \brief ServerLoopProc The Server loop process.
* \param sock The socket information
* \param addr The socket address information
*/
void ServerLoopProc(common::TCPSocket sock, common::SockAddr addr) {
// Server loop
auto env = RPCEnv();
RPCServerLoop(sock.sockfd);
LOG(INFO) << "Finish serving " << addr.AsString();
env.CleanUp();
}
/*!
* \brief GetTimeOutFromOpts Parse and get the timeout option.
* \param opts The option string
* \param timeout value after parsing.
*/
int GetTimeOutFromOpts(std::string opts) {
std::string cmd;
std::string option = "-timeout=";
if (opts.find(option) == 0) {
cmd = opts.substr(opts.find_last_of(option) + 1);
CHECK(common::IsNumber(cmd)) << "Timeout is not valid";
return std::stoi(cmd);
}
return 0;
}
std::string host_;
int port_;
int my_port_;
int port_end_;
std::string tracker_addr_;
std::string key_;
std::string custom_addr_;
common::TCPSocket listen_sock_;
common::TCPSocket tracker_sock_;
};
/*!
* \brief RPCServerCreate Creates the RPC Server.
* \param host The hostname of the server, Default=0.0.0.0
* \param port The port of the RPC, Default=9090
* \param port_end The end search port of the RPC, Default=9199
* \param tracker The address of RPC tracker in host:port format e.g. 10.77.1.234:9190 Default=""
* \param key The key used to identify the device type in tracker. Default=""
* \param custom_addr Custom IP Address to Report to RPC Tracker. Default=""
* \param silent Whether run in silent mode. Default=True
*/
void RPCServerCreate(std::string host,
int port,
int port_end,
std::string tracker_addr,
std::string key,
std::string custom_addr,
bool silent) {
if (silent) {
// Only errors and fatal is logged
dmlc::InitLogging("--minloglevel=2");
}
// Start the rpc server
RPCServer rpc(host, port, port_end, tracker_addr, key, custom_addr);
rpc.Start();
}
TVM_REGISTER_GLOBAL("rpc._ServerCreate")
.set_body([](TVMArgs args, TVMRetValue* rv) {
RPCServerCreate(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
});
} // namespace runtime
} // namespace tvm