Add support for external thread events
Add support for external thread events.
See asio_external_thread_event.h for details about usage.
This is the first version that compiled, the code was not executed even
once, but wanted to get it out early to unblock @hannesr. Thus the [RFC]
flag.
My next step is to implement asio-compatible equivalent of
fb_call_user_func_async() that will make it possible to easily test the
new functonality by simulating async stuff with sleep(), etc. on the PHP
side.
There are 2 major mostly lockless synchronization points that needs
careful review:
- queue of ready external thread events (asio_session.{cpp.h})
- shutdown cleanup (asio_external_thread_event.{cpp,h})
Esse commit está contido em:
@@ -651,6 +651,32 @@
|
||||
"desc": "A queue that is run only once there is no pending I\/O operation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ExternalThreadEventWaitHandle",
|
||||
"parent": "WaitableWaitHandle",
|
||||
"desc": "A wait handle that synchronizes against C++ operation in external thread",
|
||||
"bases": [
|
||||
"Sweepable"
|
||||
],
|
||||
"flags": [
|
||||
"HasDocComment",
|
||||
"NoDefaultSweep"
|
||||
],
|
||||
"funcs": [
|
||||
{
|
||||
"name": "__construct",
|
||||
"flags": [
|
||||
"IsPrivate",
|
||||
"HasDocComment"
|
||||
],
|
||||
"return": {
|
||||
"type": null
|
||||
},
|
||||
"args": [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ void AsioContext::exit(context_idx_t ctx_idx) {
|
||||
for (auto it : m_priorityQueueNoPendingIO) {
|
||||
exitContextQueue(ctx_idx, it.second);
|
||||
}
|
||||
|
||||
while (!m_externalThreadEvents.empty()) {
|
||||
auto ete_wh = m_externalThreadEvents.back();
|
||||
m_externalThreadEvents.pop_back();
|
||||
ete_wh->exitContext(ctx_idx);
|
||||
}
|
||||
}
|
||||
|
||||
void AsioContext::schedule(c_ContinuationWaitHandle* wait_handle) {
|
||||
@@ -68,24 +74,49 @@ void AsioContext::schedule(c_RescheduleWaitHandle* wait_handle, uint32_t queue,
|
||||
wait_handle->incRefCount();
|
||||
}
|
||||
|
||||
uint32_t AsioContext::registerExternalThreadEvent(c_ExternalThreadEventWaitHandle* wait_handle) {
|
||||
m_externalThreadEvents.push_back(wait_handle);
|
||||
return m_externalThreadEvents.size() - 1;
|
||||
}
|
||||
|
||||
void AsioContext::unregisterExternalThreadEvent(uint32_t ete_idx) {
|
||||
assert(ete_idx < m_externalThreadEvents.size());
|
||||
if (ete_idx != m_externalThreadEvents.size() - 1) {
|
||||
m_externalThreadEvents[ete_idx] = m_externalThreadEvents.back();
|
||||
m_externalThreadEvents[ete_idx]->setIndex(ete_idx);
|
||||
}
|
||||
m_externalThreadEvents.pop_back();
|
||||
}
|
||||
|
||||
void AsioContext::runUntil(c_WaitableWaitHandle* wait_handle) {
|
||||
assert(!m_current);
|
||||
assert(wait_handle);
|
||||
assert(wait_handle->getContext() == this);
|
||||
|
||||
auto session = AsioSession::Get();
|
||||
uint8_t check_ete_counter = 0;
|
||||
|
||||
while (!wait_handle->isFinished()) {
|
||||
// run queue of ready continuations
|
||||
while (!m_runnableQueue.empty()) {
|
||||
// process ready external thread events once per 256 other events
|
||||
// (when 8-bit check_ete_counter overflows)
|
||||
if (!++check_ete_counter) {
|
||||
auto ete_wh = session->getReadyExternalThreadEvents();
|
||||
while (ete_wh) {
|
||||
auto next_wh = ete_wh->getNextToProcess();
|
||||
ete_wh->process();
|
||||
ete_wh = next_wh;
|
||||
}
|
||||
}
|
||||
|
||||
// run queue of ready continuations once
|
||||
if (!m_runnableQueue.empty()) {
|
||||
auto current = m_runnableQueue.front();
|
||||
m_runnableQueue.pop();
|
||||
m_current = current;
|
||||
m_current->run();
|
||||
m_current = nullptr;
|
||||
decRefObj(current);
|
||||
|
||||
if (wait_handle->isFinished()) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// run default priority queue once
|
||||
@@ -93,6 +124,18 @@ void AsioContext::runUntil(c_WaitableWaitHandle* wait_handle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// pending external thread events? wait for at least one to become ready
|
||||
if (!m_externalThreadEvents.empty()) {
|
||||
// all your wait time are belong to us
|
||||
auto ete_wh = session->waitForExternalThreadEvents();
|
||||
while (ete_wh) {
|
||||
auto next_wh = ete_wh->getNextToProcess();
|
||||
ete_wh->process();
|
||||
ete_wh = next_wh;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// run no-pending-io priority queue once
|
||||
if (runSingle(m_priorityQueueNoPendingIO)) {
|
||||
continue;
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace HPHP {
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(WaitableWaitHandle);
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ContinuationWaitHandle);
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(RescheduleWaitHandle);
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ExternalThreadEventWaitHandle);
|
||||
|
||||
typedef uint8_t context_idx_t;
|
||||
|
||||
@@ -44,6 +45,8 @@ class AsioContext {
|
||||
|
||||
void schedule(c_ContinuationWaitHandle* wait_handle);
|
||||
void schedule(c_RescheduleWaitHandle* wait_handle, uint32_t queue, uint32_t priority);
|
||||
uint32_t registerExternalThreadEvent(c_ExternalThreadEventWaitHandle* wait_handle);
|
||||
void unregisterExternalThreadEvent(uint32_t ete_idx);
|
||||
void runUntil(c_WaitableWaitHandle* wait_handle);
|
||||
|
||||
static const uint32_t QUEUE_DEFAULT = 0;
|
||||
@@ -65,6 +68,9 @@ class AsioContext {
|
||||
|
||||
// queue of RescheduleWaitHandles scheduled to be run once there is no pending I/O
|
||||
reschedule_priority_queue_t m_priorityQueueNoPendingIO;
|
||||
|
||||
// list of all pending ExternalThreadEventWaitHandles
|
||||
smart::vector<c_ExternalThreadEventWaitHandle*> m_externalThreadEvents;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
| Copyright (c) 1997-2010 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| 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 <thread>
|
||||
#include <runtime/ext/ext_asio.h>
|
||||
#include <runtime/ext/asio/asio_external_thread_event.h>
|
||||
#include <runtime/ext/asio/asio_session.h>
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AsioExternalThreadEvent::AsioExternalThreadEvent()
|
||||
: m_session(AsioSession::Get()),
|
||||
m_state(Waiting) {
|
||||
m_waitHandle = c_ExternalThreadEventWaitHandle::Create(this);
|
||||
}
|
||||
|
||||
void AsioExternalThreadEvent::abandon() {
|
||||
assert(m_state.load() == Waiting);
|
||||
assert(m_waitHandle->getCount() == 1);
|
||||
m_waitHandle->abandon(false);
|
||||
}
|
||||
|
||||
bool AsioExternalThreadEvent::cancel() {
|
||||
uint32_t/*state_t*/ expected(Waiting);
|
||||
if (m_state.compare_exchange_strong(expected, Canceled)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(expected == Finished);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsioExternalThreadEvent::markAsFinished() {
|
||||
uint32_t/*state_t*/ expected(Waiting);
|
||||
if (m_state.compare_exchange_strong(expected, Finished)) {
|
||||
// transfer ownership
|
||||
m_session->enqueueExternalThreadEvent(m_waitHandle);
|
||||
} else {
|
||||
// web request died, destroy object
|
||||
assert(expected == Canceled);
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
| Copyright (c) 1997-2010 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| 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. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_EXT_ASIO_EXTERNAL_THREAD_EVENT_H_
|
||||
#define incl_EXT_ASIO_EXTERNAL_THREAD_EVENT_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <runtime/base/base_includes.h>
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class AsioSession;
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ExternalThreadEventWaitHandle);
|
||||
|
||||
/**
|
||||
* An asynchronous external thread event.
|
||||
*
|
||||
* A root class of all classes of objects representing events external to
|
||||
* the web request thread that synchronizes on them using ASIO framework.
|
||||
*
|
||||
* This is the preferred mechanism of I/O integration with ASIO framework.
|
||||
*
|
||||
* To implement a new class of such events, define a subclass that stores
|
||||
* information needed to unserialize result and provides a memory for storing
|
||||
* raw result. Then define a method that populates the result and marks
|
||||
* the event as finished, override unserialize method that will produce
|
||||
* result in the format understood by HHVM and implement destructor that
|
||||
* will clean up the stored data.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* class FooEvent : public AsioExternalThreadEvent {
|
||||
* public:
|
||||
* FooEvent(int max_value) : m_maxValue(max_value), m_failed(false) {}
|
||||
* ~FooEvent() {}
|
||||
* void setResult(int value) {
|
||||
* m_value = value;
|
||||
* markAsFinished();
|
||||
* }
|
||||
* void setException(const FooException& exception) {
|
||||
* m_failed = true;
|
||||
* markAsFinished();
|
||||
* }
|
||||
* protected:
|
||||
* void unserialize(TypedValue* result) const {
|
||||
* if (UNLIKELY(m_failed)) {
|
||||
* Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
||||
* "An error has occurred while scheduling the operation"));
|
||||
* throw e;
|
||||
* }
|
||||
*
|
||||
* if (UNLIKELY(m_value > m_maxValue)) {
|
||||
* Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
||||
* "Invalid response returned by Foo backend"));
|
||||
* throw e;
|
||||
* }
|
||||
*
|
||||
* result->m_type = KindOfInt64;
|
||||
* result->m_data.num = m_value;
|
||||
* }
|
||||
* private:
|
||||
* int m_value, m_maxValue;
|
||||
* bool m_failed;
|
||||
* };
|
||||
*
|
||||
* To use this mechanism from your extension, create an instance of the class,
|
||||
* schedule an asynchronous operation and return the wait handle to the caller.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Object f_gen_foo(int max_value) {
|
||||
* // validate user input early
|
||||
* if (max_value < 0) {
|
||||
* Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
* "Expected max_value to be non-negative"));
|
||||
* throw e;
|
||||
* }
|
||||
*
|
||||
* FooEvent* event = new FooEvent(max_value);
|
||||
* try {
|
||||
* // make foo run asynchronously and eventually call event->setResult()
|
||||
* foo_async_schedule(event);
|
||||
* } catch (const FooException& exception) {
|
||||
* // handle error while trying to schedule requested operation
|
||||
* event->setException(exception);
|
||||
* } catch (...) {
|
||||
* // unknown exception; should be never reached
|
||||
* assert(false);
|
||||
* event->abandon();
|
||||
* Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
||||
* "Encountered unexpected exception"));
|
||||
* throw e;
|
||||
* }
|
||||
* return event->getWaitHandle();
|
||||
* }
|
||||
*
|
||||
* Caveats:
|
||||
* - web request may die before the event is finished; never store pointers
|
||||
* to any data owned by PHP as the PHP thread may die at any time
|
||||
*/
|
||||
class AsioExternalThreadEvent {
|
||||
public:
|
||||
/**
|
||||
* Get wait handle representing this external thread event.
|
||||
*
|
||||
* This function may be called only from the web request thread between
|
||||
* construction of this object and return of the control back to the VM.
|
||||
* Do not try to call this after any PHP code got executed; the asynchronous
|
||||
* operation may have finished and this object could have been already
|
||||
* destroyed.
|
||||
*
|
||||
* The caller is responsible for obtaining a reference count immediately
|
||||
* after obtaining the pointer (e.g. by type casting this into Object,
|
||||
* populating a TypedValue using tvWriteObject, or setting an array
|
||||
* element). If any PHP code is executed, a bad things may happen.
|
||||
*
|
||||
* It is okay to call this after asynchronous operation was scheduled.
|
||||
* Even if the operation has finished, the object is not destroyed until
|
||||
* ASIO main loop is executed.
|
||||
*/
|
||||
c_ExternalThreadEventWaitHandle* getWaitHandle() {
|
||||
return m_waitHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandon this external thread event.
|
||||
*
|
||||
* Abandons this event and frees all associated resources before any action
|
||||
* is taken. Useful if a caller changed its mind after this event was
|
||||
* constructed (e.g. due to failed transfer of ownership to the processing
|
||||
* thread).
|
||||
*
|
||||
* This function may be called only from the web request thread between
|
||||
* construction of this object and return of the control back to the VM,
|
||||
* assuming that no other methods of this object were called prior to this
|
||||
* call (except for constructor). Once this function is called, all
|
||||
* associated resources are reclaimed and it is illegal to perform any
|
||||
* further operation on this object.
|
||||
*/
|
||||
void abandon();
|
||||
|
||||
/**
|
||||
* Cancel waiting for the event. INTERNAL USE ONLY. DO NOT CALL.
|
||||
*
|
||||
* Returns true iff we transitioned from Waiting to Canceled; otherwise,
|
||||
* this function waits until processing thread completed its transition
|
||||
* to the Finished state.
|
||||
*
|
||||
* This function is called internally from the web request thread to signal
|
||||
* that the result will not be retrieved by the web request thread.
|
||||
*
|
||||
* This is used in rare case when the web request thread is dying without
|
||||
* waiting for the asynchronous operation to finish.
|
||||
*/
|
||||
bool cancel();
|
||||
|
||||
/**
|
||||
* Destroy the object. INTERNAL USE ONLY. DO NOT CALL.
|
||||
*
|
||||
* The purpose of this function is to make destructor protected so that
|
||||
* this object is not accidentally used in conjunction with shared pointers.
|
||||
*/
|
||||
void release() { delete this; }
|
||||
|
||||
/**
|
||||
* Unserialize result. Implemented by subclasses.
|
||||
*
|
||||
* Unserializes result and writes it to the provided uninitialized
|
||||
* TypedValue variable.
|
||||
*
|
||||
* This function is optionally called internally from the web request thread
|
||||
* to retrieve result of the operation after markAsFinished() was called
|
||||
* to signal the result is ready.
|
||||
*
|
||||
* A failed operation may be signaled by throwing a PHP exception instead
|
||||
* of populating result variable. Result variable will be ignored.
|
||||
* If a result was already initialized, it must be uninitialized (decref
|
||||
* if needed) prior to throwing an exception.
|
||||
*/
|
||||
virtual void unserialize(TypedValue* result) const = 0;
|
||||
|
||||
protected:
|
||||
AsioExternalThreadEvent();
|
||||
|
||||
/**
|
||||
* Destruct AsioExternalThreadEvent
|
||||
*
|
||||
* Object lifetime and ownership is managed internally. Do not try to
|
||||
* destruct this object yourself. Instead, make sure markAsFinished()
|
||||
* is eventually called.
|
||||
*/
|
||||
virtual ~AsioExternalThreadEvent() {
|
||||
assert(m_state.load() == Finished || m_state.load() == Canceled);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark the event as finished.
|
||||
*
|
||||
* Marks event as finished and transfers ownership of this object to
|
||||
* the web request thread.
|
||||
*
|
||||
* This function may be called only from the processing thread to signal
|
||||
* that the result is ready to be unserialized. Once this function is
|
||||
* called, unserialize() function or object destructor may be called
|
||||
* at any time and the processing thread should not do any further
|
||||
* operations on this object.
|
||||
*/
|
||||
void markAsFinished();
|
||||
|
||||
private:
|
||||
enum state_t : uint32_t {
|
||||
/**
|
||||
* Web request thread waiting for processing thread to finish.
|
||||
*
|
||||
* This object is owned by processing thread, which is responsible for
|
||||
* eventually calling markAsFinished().
|
||||
*/
|
||||
Waiting,
|
||||
|
||||
/**
|
||||
* Web request thread scheduled to unserialize result.
|
||||
*
|
||||
* This object is owned by web request thread, which is responsible for
|
||||
* destruction of this object after optional unserialization.
|
||||
*/
|
||||
Finished,
|
||||
|
||||
/**
|
||||
* Web request thread died before processing thread finished.
|
||||
*
|
||||
* This object is owned by processing thread, which is responsible for
|
||||
* eventually calling markAsFinished() that will destruct this object.
|
||||
*/
|
||||
Canceled,
|
||||
};
|
||||
|
||||
AsioSession* m_session;
|
||||
c_ExternalThreadEventWaitHandle* m_waitHandle;
|
||||
std::atomic<uint32_t/*state_t*/> m_state;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
#endif // incl_EXT_ASIO_EXTERNAL_THREAD_EVENT_H_
|
||||
@@ -33,7 +33,10 @@ void AsioSession::Init() {
|
||||
}
|
||||
|
||||
AsioSession::AsioSession()
|
||||
: m_contexts(), m_onFailedCallback(nullptr), m_onStartedCallback(nullptr) {
|
||||
: m_contexts(), m_readyExternalThreadEvents(nullptr),
|
||||
m_readyExternalThreadEventsMutex(),
|
||||
m_readyExternalThreadEventsCondition(),
|
||||
m_onFailedCallback(nullptr), m_onStartedCallback(nullptr) {
|
||||
}
|
||||
|
||||
void AsioSession::enterContext() {
|
||||
@@ -68,6 +71,53 @@ uint16_t AsioSession::getCurrentWaitHandleDepth() {
|
||||
return isInContext() ? getCurrentWaitHandle()->getDepth() : 0;
|
||||
}
|
||||
|
||||
c_ExternalThreadEventWaitHandle* AsioSession::waitForExternalThreadEvents() {
|
||||
// try check for ready external thread events without grabbing lock
|
||||
auto ready = m_readyExternalThreadEvents.exchange(nullptr);
|
||||
if (ready != nullptr) {
|
||||
assert(ready != k_waitingForExternalThreadEvents);
|
||||
return ready;
|
||||
}
|
||||
|
||||
// no ready external thread events available, synchronization needed
|
||||
std::unique_lock<std::mutex> lock(m_readyExternalThreadEventsMutex);
|
||||
|
||||
// transition from empty to WAITING
|
||||
if (m_readyExternalThreadEvents.compare_exchange_strong(ready, k_waitingForExternalThreadEvents)) {
|
||||
// wait for transition from WAITING to non-empty
|
||||
do {
|
||||
m_readyExternalThreadEventsCondition.wait(lock);
|
||||
} while (m_readyExternalThreadEvents.load() == k_waitingForExternalThreadEvents);
|
||||
} else {
|
||||
// external thread transitioned from empty to non-empty while grabbing lock
|
||||
}
|
||||
|
||||
ready = m_readyExternalThreadEvents.exchange(nullptr);
|
||||
assert(ready != nullptr);
|
||||
assert(ready != k_waitingForExternalThreadEvents);
|
||||
return ready;
|
||||
}
|
||||
|
||||
void AsioSession::enqueueExternalThreadEvent(c_ExternalThreadEventWaitHandle* wait_handle) {
|
||||
auto next = m_readyExternalThreadEvents.load();
|
||||
while (true) {
|
||||
while (next != k_waitingForExternalThreadEvents) {
|
||||
wait_handle->setNextToProcess(next);
|
||||
if (m_readyExternalThreadEvents.compare_exchange_weak(next, wait_handle)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// try to transition from WAITING to non-empty
|
||||
wait_handle->setNextToProcess(nullptr);
|
||||
if (m_readyExternalThreadEvents.compare_exchange_weak(next, wait_handle)) {
|
||||
// succeeded, notify condition
|
||||
m_readyExternalThreadEventsCondition.notify_one();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsioSession::setOnFailedCallback(ObjectData* on_failed_callback) {
|
||||
if (on_failed_callback) {
|
||||
on_failed_callback->incRefCount();
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#ifndef incl_HPHP_EXT_ASIO_SESSION_H_
|
||||
#define incl_HPHP_EXT_ASIO_SESSION_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <runtime/base/base_includes.h>
|
||||
#include <runtime/ext/asio/asio_context.h>
|
||||
|
||||
@@ -25,6 +28,7 @@ namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ContinuationWaitHandle);
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ExternalThreadEventWaitHandle);
|
||||
|
||||
class AsioSession {
|
||||
public:
|
||||
@@ -64,6 +68,16 @@ class AsioSession {
|
||||
|
||||
uint16_t getCurrentWaitHandleDepth();
|
||||
|
||||
// external thread event
|
||||
c_ExternalThreadEventWaitHandle* getReadyExternalThreadEvents() {
|
||||
auto ready = m_readyExternalThreadEvents.exchange(nullptr);
|
||||
assert(ready != k_waitingForExternalThreadEvents);
|
||||
return ready;
|
||||
}
|
||||
|
||||
c_ExternalThreadEventWaitHandle* waitForExternalThreadEvents();
|
||||
void enqueueExternalThreadEvent(c_ExternalThreadEventWaitHandle* wait_handle);
|
||||
|
||||
// callback: on failed
|
||||
void setOnFailedCallback(ObjectData* on_failed_callback);
|
||||
void onFailed(CObjRef exception);
|
||||
@@ -76,9 +90,16 @@ class AsioSession {
|
||||
private:
|
||||
static DECLARE_THREAD_LOCAL_PROXY(AsioSession, false, s_current);
|
||||
|
||||
static constexpr c_ExternalThreadEventWaitHandle* k_waitingForExternalThreadEvents = static_cast<c_ExternalThreadEventWaitHandle*>((void*)1L);
|
||||
|
||||
AsioSession();
|
||||
|
||||
smart::vector<AsioContext*> m_contexts;
|
||||
|
||||
std::atomic<c_ExternalThreadEventWaitHandle*> m_readyExternalThreadEvents;
|
||||
std::mutex m_readyExternalThreadEventsMutex;
|
||||
std::condition_variable m_readyExternalThreadEventsCondition;
|
||||
|
||||
ObjectData* m_onFailedCallback;
|
||||
ObjectData* m_onStartedCallback;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
| Copyright (c) 1997-2010 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| 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 <runtime/ext/ext_asio.h>
|
||||
#include <runtime/ext/asio/asio_external_thread_event.h>
|
||||
#include <runtime/ext/asio/asio_session.h>
|
||||
#include <system/lib/systemlib.h>
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
StaticString s_externalThreadEvent("<external-thread-event>");
|
||||
}
|
||||
|
||||
c_ExternalThreadEventWaitHandle::c_ExternalThreadEventWaitHandle(VM::Class *cb)
|
||||
: c_WaitableWaitHandle(cb) {
|
||||
}
|
||||
|
||||
c_ExternalThreadEventWaitHandle::~c_ExternalThreadEventWaitHandle() {
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::sweep() {
|
||||
assert(getState() == STATE_WAITING);
|
||||
|
||||
if (m_event->cancel()) {
|
||||
// canceled; the processing thread will take care of cleanup
|
||||
return;
|
||||
}
|
||||
|
||||
// event has finished, but process() was not called yet
|
||||
auto session = AsioSession::Get();
|
||||
bool done = false;
|
||||
do {
|
||||
auto ete_wh = session->waitForExternalThreadEvents();
|
||||
while (ete_wh) {
|
||||
done |= ete_wh == this;
|
||||
auto next_wh = ete_wh->getNextToProcess();
|
||||
ete_wh->abandon(true);
|
||||
ete_wh = next_wh;
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::t___construct() {
|
||||
Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
||||
"ExternalThreadEventWaitHandle can be constructed only from extension"));
|
||||
throw e;
|
||||
}
|
||||
|
||||
c_ExternalThreadEventWaitHandle* c_ExternalThreadEventWaitHandle::Create(AsioExternalThreadEvent* event) {
|
||||
c_ExternalThreadEventWaitHandle* wh = NEWOBJ(c_ExternalThreadEventWaitHandle);
|
||||
wh->initialize(event);
|
||||
return wh;
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::initialize(AsioExternalThreadEvent* event) {
|
||||
// this wait handle is owned by existence of unprocessed event
|
||||
incRefCount();
|
||||
m_event = event;
|
||||
|
||||
setState(STATE_WAITING);
|
||||
if (isInContext()) {
|
||||
m_index = getContext()->registerExternalThreadEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::abandon(bool sweeping) {
|
||||
assert(getState() == STATE_WAITING);
|
||||
assert(getCount() == 1 || sweeping);
|
||||
|
||||
if (isInContext()) {
|
||||
getContext()->unregisterExternalThreadEvent(m_index);
|
||||
}
|
||||
|
||||
// event is abandoned, destroy it, unregister sweepable and decref ownership
|
||||
m_event->release();
|
||||
m_event = nullptr;
|
||||
unregister();
|
||||
decRefObj(this);
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::process() {
|
||||
assert(getState() == STATE_WAITING);
|
||||
|
||||
if (isInContext()) {
|
||||
getContext()->unregisterExternalThreadEvent(m_index);
|
||||
}
|
||||
|
||||
try {
|
||||
TypedValue result;
|
||||
m_event->unserialize(&result);
|
||||
assert(tvIsPlausible(&result));
|
||||
setResult(&result);
|
||||
tvRefcountedDecRefCell(&result);
|
||||
} catch (const Object& exception) {
|
||||
setException(exception.get());
|
||||
}
|
||||
|
||||
// event is processed, destroy it, unregister sweepable and decref ownership
|
||||
m_event->release();
|
||||
m_event = nullptr;
|
||||
unregister();
|
||||
decRefObj(this);
|
||||
}
|
||||
|
||||
String c_ExternalThreadEventWaitHandle::getName() {
|
||||
return s_externalThreadEvent;
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::enterContext(context_idx_t ctx_idx) {
|
||||
assert(AsioSession::Get()->getContext(ctx_idx));
|
||||
|
||||
// stop before corrupting unioned data
|
||||
if (isFinished()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// already in the more specific context?
|
||||
if (LIKELY(getContextIdx() >= ctx_idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(getState() == STATE_WAITING);
|
||||
|
||||
if (isInContext()) {
|
||||
getContext()->unregisterExternalThreadEvent(m_index);
|
||||
}
|
||||
|
||||
setContextIdx(ctx_idx);
|
||||
m_index = getContext()->registerExternalThreadEvent(this);
|
||||
}
|
||||
|
||||
void c_ExternalThreadEventWaitHandle::exitContext(context_idx_t ctx_idx) {
|
||||
assert(AsioSession::Get()->getContext(ctx_idx));
|
||||
assert(getContextIdx() == ctx_idx);
|
||||
assert(getState() == STATE_WAITING);
|
||||
|
||||
// move us to the parent context
|
||||
setContextIdx(getContextIdx() - 1);
|
||||
|
||||
// re-register if still in a context
|
||||
if (isInContext()) {
|
||||
getContext()->registerExternalThreadEvent(this);
|
||||
}
|
||||
|
||||
// recursively move all wait handles blocked by us
|
||||
for (auto pwh = getFirstParent(); pwh; pwh = pwh->getNextParent()) {
|
||||
pwh->exitContextBlocked(ctx_idx);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
@@ -433,6 +433,48 @@ class c_RescheduleWaitHandle : public c_WaitableWaitHandle {
|
||||
static const int8_t STATE_SCHEDULED = 3;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// class ExternalThreadEventWaitHandle
|
||||
|
||||
/**
|
||||
* A wait handle that synchronizes against C++ operation in external thread.
|
||||
*
|
||||
* See asio_external_thread_event.h for more details.
|
||||
*/
|
||||
class AsioExternalThreadEvent;
|
||||
FORWARD_DECLARE_CLASS_BUILTIN(ExternalThreadEventWaitHandle);
|
||||
class c_ExternalThreadEventWaitHandle : public c_WaitableWaitHandle, public Sweepable {
|
||||
public:
|
||||
DECLARE_CLASS(ExternalThreadEventWaitHandle, ExternalThreadEventWaitHandle, WaitableWaitHandle)
|
||||
|
||||
// need to implement
|
||||
public: c_ExternalThreadEventWaitHandle(VM::Class* cls = c_ExternalThreadEventWaitHandle::s_cls);
|
||||
public: ~c_ExternalThreadEventWaitHandle();
|
||||
public: void t___construct();
|
||||
|
||||
public:
|
||||
static c_ExternalThreadEventWaitHandle* Create(AsioExternalThreadEvent* event);
|
||||
|
||||
c_ExternalThreadEventWaitHandle* getNextToProcess() { assert(getState() == STATE_WAITING); return m_nextToProcess; }
|
||||
void setNextToProcess(c_ExternalThreadEventWaitHandle* next) { assert(getState() == STATE_WAITING); m_nextToProcess = next; }
|
||||
void setIndex(uint32_t ete_idx) { assert(getState() == STATE_WAITING); m_index = ete_idx; }
|
||||
|
||||
void abandon(bool sweeping);
|
||||
void process();
|
||||
String getName();
|
||||
void enterContext(context_idx_t ctx_idx);
|
||||
void exitContext(context_idx_t ctx_idx);
|
||||
|
||||
private:
|
||||
void initialize(AsioExternalThreadEvent* event);
|
||||
|
||||
c_ExternalThreadEventWaitHandle* m_nextToProcess;
|
||||
AsioExternalThreadEvent* m_event;
|
||||
uint32_t m_index;
|
||||
|
||||
static const uint8_t STATE_WAITING = 3;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
@@ -24931,6 +24931,18 @@ const char *g_class_map[] = {
|
||||
"QUEUE_NO_PENDING_IO", (const char*)&q_RescheduleWaitHandle$$QUEUE_NO_PENDING_IO, (const char *)0xc /* KindOfInt64 */,
|
||||
NULL,
|
||||
NULL,
|
||||
(const char *)0x18006000, "ExternalThreadEventWaitHandle", "waitablewaithandle", "", (const char *)0, (const char *)0,
|
||||
"/**\n * ( excerpt from\n * http://php.net/manual/en/class.externalthreadeventwaithandle.php )\n *\n * A wait handle that synchronizes against C++ operation in external\n * thread\n *\n */",
|
||||
NULL,
|
||||
(const char *)0x10006100, "__construct", "", (const char*)0, (const char*)0,
|
||||
"/**\n * ( excerpt from\n * http://php.net/manual/en/externalthreadeventwaithandle.construct.php )\n *\n *\n */",
|
||||
(const char *)0x8 /* KindOfNull */, NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(const char *)0x10006000, "UConverter", "", "", (const char *)0, (const char *)0,
|
||||
"/**\n * ( excerpt from http://php.net/manual/en/class.uconverter.php )\n *\n * ICU UConverter class\n *\n */",
|
||||
NULL,
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário