/hphp/runtime/server/xbox-server.cpp
C++ | 430 lines | 336 code | 67 blank | 27 comment | 52 complexity | 93a15ffb0c824f4f3cc3b9129f5a3fd5 MD5 | raw file
- /*
- +----------------------------------------------------------------------+
- | HipHop for PHP |
- +----------------------------------------------------------------------+
- | Copyright (c) 2010-2016 Facebook, Inc. (http://www.facebook.com) |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 of the PHP license, |
- | that is bundled with this package in the file LICENSE, and is |
- | available through the world-wide-web at the following url: |
- | http://www.php.net/license/3_01.txt |
- | If you did not receive a copy of the PHP license and are unable to |
- | obtain it through the world-wide-web, please send a note to |
- | license@php.net so we can mail you a copy immediately. |
- +----------------------------------------------------------------------+
- */
- #include "hphp/runtime/server/xbox-server.h"
- #include "hphp/runtime/base/builtin-functions.h"
- #include "hphp/runtime/base/comparisons.h"
- #include "hphp/runtime/base/runtime-option.h"
- #include "hphp/runtime/server/rpc-request-handler.h"
- #include "hphp/runtime/server/satellite-server.h"
- #include "hphp/runtime/base/libevent-http-client.h"
- #include "hphp/runtime/server/job-queue-vm-stack.h"
- #include "hphp/runtime/server/server-task-event.h"
- #include "hphp/util/job-queue.h"
- #include "hphp/util/lock.h"
- #include "hphp/util/logger.h"
- #include "hphp/util/timer.h"
- #include "hphp/system/systemlib.h"
- namespace HPHP {
- ///////////////////////////////////////////////////////////////////////////////
- using std::string;
- XboxTransport::XboxTransport(const String& message,
- const String& reqInitDoc /* = "" */)
- : m_refCount(0), m_done(false), m_code(0), m_event(nullptr) {
- Timer::GetMonotonicTime(m_queueTime);
- m_message.append(message.data(), message.size());
- m_reqInitDoc.append(reqInitDoc.data(), reqInitDoc.size());
- disableCompression(); // so we don't have to decompress during sendImpl()
- }
- const char *XboxTransport::getUrl() {
- if (!m_reqInitDoc.empty()) {
- return "xbox_process_call_message";
- }
- return RuntimeOption::XboxProcessMessageFunc.c_str();
- }
- std::string XboxTransport::getHeader(const char *name) {
- if (!strcasecmp(name, "Host")) return m_host;
- if (!strcasecmp(name, "ReqInitDoc")) return m_reqInitDoc;
- return "";
- }
- void XboxTransport::sendImpl(const void *data, int size, int code,
- bool chunked, bool eom) {
- m_response.append((const char*)data, size);
- if (code) {
- m_code = code;
- }
- if (eom) {
- onSendEndImpl();
- }
- }
- void XboxTransport::onSendEndImpl() {
- Lock lock(this);
- if (m_done) {
- return;
- }
- m_done = true;
- if (m_event) {
- m_event->finish();
- }
- notify();
- }
- String XboxTransport::getResults(int &code, int timeout_ms /* = 0 */) {
- {
- Lock lock(this);
- while (!m_done) {
- if (timeout_ms > 0) {
- long long seconds = timeout_ms / 1000;
- long long nanosecs = (timeout_ms - seconds * 1000) * 1000;
- if (!wait(seconds, nanosecs)) {
- code = -1;
- return empty_string();
- }
- } else {
- wait();
- }
- }
- }
- String response(m_response.c_str(), m_response.size(), CopyString);
- code = m_code;
- return response;
- }
- ///////////////////////////////////////////////////////////////////////////////
- static IMPLEMENT_THREAD_LOCAL(std::shared_ptr<XboxServerInfo>,
- s_xbox_server_info);
- static IMPLEMENT_THREAD_LOCAL(std::string, s_xbox_prev_req_init_doc);
- struct XboxRequestHandler : RPCRequestHandler {
- XboxRequestHandler() : RPCRequestHandler(
- (*s_xbox_server_info)->getTimeoutSeconds().count(), Info) {}
- static bool Info;
- };
- bool XboxRequestHandler::Info = false;
- static IMPLEMENT_THREAD_LOCAL(XboxRequestHandler, s_xbox_request_handler);
- ///////////////////////////////////////////////////////////////////////////////
- struct XboxWorker
- : JobQueueWorker<XboxTransport*,Server*,true,false,JobQueueDropVMStack>
- {
- virtual void doJob(XboxTransport *job) {
- try {
- // If this job or the previous job that ran on this thread have
- // a custom initial document, make sure we do a reset
- string reqInitDoc = job->getHeader("ReqInitDoc");
- *s_xbox_prev_req_init_doc = reqInitDoc;
- job->onRequestStart(job->getStartTimer());
- createRequestHandler()->run(job);
- destroyRequestHandler();
- job->decRefCount();
- } catch (...) {
- Logger::Error("RpcRequestHandler leaked exceptions");
- }
- }
- private:
- RequestHandler *createRequestHandler() {
- if (!*s_xbox_server_info) {
- *s_xbox_server_info = std::make_shared<XboxServerInfo>();
- }
- if (RuntimeOption::XboxServerLogInfo) XboxRequestHandler::Info = true;
- s_xbox_request_handler->setServerInfo(*s_xbox_server_info);
- s_xbox_request_handler->setReturnEncodeType(
- RPCRequestHandler::ReturnEncodeType::Serialize);
- return s_xbox_request_handler.get();
- }
- void destroyRequestHandler() {
- if (!s_xbox_request_handler.isNull()) {
- s_xbox_request_handler.destroy();
- }
- }
- virtual void onThreadExit() {
- if (!s_xbox_request_handler.isNull()) {
- s_xbox_request_handler.destroy();
- }
- }
- };
- ///////////////////////////////////////////////////////////////////////////////
- static JobQueueDispatcher<XboxWorker> *s_dispatcher;
- static Mutex s_dispatchMutex;
- void XboxServer::Restart() {
- Stop();
- if (RuntimeOption::XboxServerThreadCount > 0) {
- {
- Lock l(s_dispatchMutex);
- s_dispatcher = new JobQueueDispatcher<XboxWorker>
- (RuntimeOption::XboxServerThreadCount,
- RuntimeOption::ServerThreadRoundRobin,
- RuntimeOption::ServerThreadDropCacheTimeoutSeconds,
- RuntimeOption::ServerThreadDropStack,
- nullptr);
- }
- if (RuntimeOption::XboxServerLogInfo) {
- Logger::Info("xbox server started");
- }
- s_dispatcher->start();
- }
- }
- void XboxServer::Stop() {
- if (s_dispatcher) {
- s_dispatcher->stop();
- Lock l(s_dispatchMutex);
- delete s_dispatcher;
- s_dispatcher = nullptr;
- }
- }
- ///////////////////////////////////////////////////////////////////////////////
- const StaticString
- s_code("code"),
- s_response("response"),
- s_error("error"),
- s_localhost("localhost"),
- s_127_0_0_1("127.0.0.1");
- static bool isLocalHost(const String& host) {
- return host.empty() || host == s_localhost || host == s_127_0_0_1;
- }
- bool XboxServer::SendMessage(const String& message,
- Array& ret,
- int timeout_ms,
- const String& host /* = "localhost" */) {
- if (isLocalHost(host)) {
- XboxTransport *job;
- {
- Lock l(s_dispatchMutex);
- if (!s_dispatcher) {
- return false;
- }
- job = new XboxTransport(message);
- job->incRefCount(); // paired with worker's decRefCount()
- job->incRefCount(); // paired with decRefCount() at below
- assert(s_dispatcher);
- s_dispatcher->enqueue(job);
- }
- if (timeout_ms <= 0) {
- timeout_ms = RuntimeOption::XboxDefaultLocalTimeoutMilliSeconds;
- }
- int code = 0;
- String response = job->getResults(code, timeout_ms);
- job->decRefCount(); // i'm done with this job
- if (code > 0) {
- ret.set(s_code, code);
- if (code == 200) {
- ret.set(s_response, unserialize_from_string(response));
- } else {
- ret.set(s_error, response);
- }
- return true;
- }
- } else { // remote
- string url = "http://";
- url += host.data();
- url += '/';
- url += RuntimeOption::XboxProcessMessageFunc;
- int timeoutSeconds = timeout_ms / 1000;
- if (timeoutSeconds <= 0) {
- timeoutSeconds = RuntimeOption::XboxDefaultRemoteTimeoutSeconds;
- }
- string hostStr(host.data());
- std::vector<std::string> headers;
- LibEventHttpClientPtr http =
- LibEventHttpClient::Get(hostStr, RuntimeOption::XboxServerPort);
- if (http->send(url, headers, timeoutSeconds, false,
- message.data(), message.size())) {
- int code = http->getCode();
- if (code > 0) {
- int len = 0;
- char *response = http->recv(len);
- String sresponse(response, len, AttachString);
- ret.set(s_code, code);
- if (code == 200) {
- ret.set(s_response, unserialize_from_string(sresponse));
- } else {
- ret.set(s_error, sresponse);
- }
- return true;
- }
- // code wasn't correctly set by http client, treat it as not found
- ret.set(s_code, 404);
- ret.set(s_error, "http client failed");
- }
- }
- return false;
- }
- bool XboxServer::PostMessage(const String& message,
- const String& host /* = "localhost" */) {
- if (isLocalHost(host)) {
- Lock l(s_dispatchMutex);
- if (!s_dispatcher) {
- return false;
- }
- XboxTransport *job = new XboxTransport(message);
- job->incRefCount(); // paired with worker's decRefCount()
- assert(s_dispatcher);
- s_dispatcher->enqueue(job);
- return true;
- } else { // remote
- string url = "http://";
- url += host.data();
- url += "/xbox_post_message";
- std::vector<std::string> headers;
- std::string hostStr(host.data());
- LibEventHttpClientPtr http =
- LibEventHttpClient::Get(hostStr, RuntimeOption::XboxServerPort);
- if (http->send(url, headers, 0, false, message.data(), message.size())) {
- int code = http->getCode();
- if (code > 0) {
- int len = 0;
- char *response = http->recv(len);
- String sresponse(response, len, AttachString);
- if (code == 200 && same(unserialize_from_string(sresponse), true)) {
- return true;
- }
- }
- }
- }
- return false;
- }
- ///////////////////////////////////////////////////////////////////////////////
- struct XboxTask : SweepableResourceData {
- DECLARE_RESOURCE_ALLOCATION(XboxTask)
- XboxTask(const String& message, const String& reqInitDoc = "") {
- m_job = new XboxTransport(message, reqInitDoc);
- m_job->incRefCount();
- }
- ~XboxTask() {
- m_job->decRefCount();
- }
- XboxTransport *getJob() { return m_job;}
- CLASSNAME_IS("XboxTask");
- // overriding ResourceData
- const String& o_getClassNameHook() const override { return classnameof(); }
- private:
- XboxTransport *m_job;
- };
- IMPLEMENT_RESOURCE_ALLOCATION(XboxTask)
- ///////////////////////////////////////////////////////////////////////////////
- Resource XboxServer::TaskStart(const String& msg,
- const String& reqInitDoc /* = "" */,
- ServerTaskEvent<XboxServer, XboxTransport> *event /* = nullptr */) {
- {
- Lock l(s_dispatchMutex);
- if (s_dispatcher &&
- (s_dispatcher->getActiveWorker() <
- RuntimeOption::XboxServerThreadCount ||
- s_dispatcher->getQueuedJobs() <
- RuntimeOption::XboxServerMaxQueueLength)) {
- auto task = req::make<XboxTask>(msg, reqInitDoc);
- XboxTransport *job = task->getJob();
- job->incRefCount(); // paired with worker's decRefCount()
- Transport *transport = g_context->getTransport();
- if (transport) {
- job->setHost(transport->getHeader("Host"));
- }
- if (event) {
- job->setAsioEvent(event);
- event->setJob(job);
- }
- assert(s_dispatcher);
- s_dispatcher->enqueue(job);
- return Resource(std::move(task));
- }
- }
- const char* errMsg =
- (RuntimeOption::XboxServerThreadCount > 0 ?
- "Cannot create new Xbox task because the Xbox queue has "
- "reached maximum capacity" :
- "Cannot create new Xbox task because the Xbox is not enabled");
- throw_exception(SystemLib::AllocExceptionObject(errMsg));
- return Resource();
- }
- bool XboxServer::TaskStatus(const Resource& task) {
- return cast<XboxTask>(task)->getJob()->isDone();
- }
- int XboxServer::TaskResult(const Resource& task, int timeout_ms, Variant *ret) {
- return TaskResult(cast<XboxTask>(task)->getJob(), timeout_ms, ret);
- }
- int XboxServer::TaskResult(XboxTransport *job, int timeout_ms, Variant *ret) {
- int code = 0;
- String response = job->getResults(code, timeout_ms);
- if (ret) {
- if (code == 200) {
- *ret = unserialize_from_string(response);
- } else {
- *ret = response;
- }
- }
- return code;
- }
- std::shared_ptr<XboxServerInfo> XboxServer::GetServerInfo() {
- return *s_xbox_server_info;
- }
- RPCRequestHandler *XboxServer::GetRequestHandler() {
- if (s_xbox_request_handler.isNull()) return nullptr;
- return s_xbox_request_handler.get();
- }
- ///////////////////////////////////////////////////////////////////////////////
- }