diff --git a/ext-src/php_swoole.cc b/ext-src/php_swoole.cc index 6331e0d0e54..cc82067679b 100644 --- a/ext-src/php_swoole.cc +++ b/ext-src/php_swoole.cc @@ -766,6 +766,7 @@ PHP_MINIT_FUNCTION(swoole) { php_swoole_thread_minit(module_number); php_swoole_thread_atomic_minit(module_number); php_swoole_thread_lock_minit(module_number); + php_swoole_thread_barrier_minit(module_number); php_swoole_thread_queue_minit(module_number); php_swoole_thread_map_minit(module_number); php_swoole_thread_arraylist_minit(module_number); diff --git a/ext-src/php_swoole_private.h b/ext-src/php_swoole_private.h index 85c29f945e7..7e84edffedd 100644 --- a/ext-src/php_swoole_private.h +++ b/ext-src/php_swoole_private.h @@ -271,6 +271,7 @@ void php_swoole_name_resolver_minit(int module_number); void php_swoole_thread_minit(int module_number); void php_swoole_thread_atomic_minit(int module_number); void php_swoole_thread_lock_minit(int module_number); +void php_swoole_thread_barrier_minit(int module_number); void php_swoole_thread_queue_minit(int module_number); void php_swoole_thread_map_minit(int module_number); void php_swoole_thread_arraylist_minit(int module_number); diff --git a/ext-src/stubs/php_swoole_thread_barrier.stub.php b/ext-src/stubs/php_swoole_thread_barrier.stub.php new file mode 100644 index 00000000000..29281c63c03 --- /dev/null +++ b/ext-src/stubs/php_swoole_thread_barrier.stub.php @@ -0,0 +1,8 @@ + | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole_private.h" +#include "php_swoole_thread.h" +#include "swoole_memory.h" +#include "swoole_lock.h" + +#ifdef SW_THREAD + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_thread_barrier_arginfo.h" +END_EXTERN_C() + +using swoole::Barrier; + +static zend_class_entry *swoole_thread_barrier_ce; +static zend_object_handlers swoole_thread_barrier_handlers; + +struct BarrierResource : public ThreadResource { + Barrier barrier_; + BarrierResource(int count) : ThreadResource() { + barrier_.init(false, count); + } + void wait() { + barrier_.wait(); + } + ~BarrierResource() { + barrier_.destroy(); + } +}; + +struct BarrierObject { + BarrierResource *barrier; + zend_object std; +}; + +static sw_inline BarrierObject *php_swoole_thread_barrier_fetch_object(zend_object *obj) { + return (BarrierObject *) ((char *) obj - swoole_thread_barrier_handlers.offset); +} + +static BarrierResource *php_swoole_thread_barrier_get_ptr(zval *zobject) { + return php_swoole_thread_barrier_fetch_object(Z_OBJ_P(zobject))->barrier; +} + +static BarrierResource *php_swoole_thread_barrier_get_and_check_ptr(zval *zobject) { + BarrierResource *barrier = php_swoole_thread_barrier_get_ptr(zobject); + if (!barrier) { + php_swoole_fatal_error(E_ERROR, "must call constructor first"); + } + return barrier; +} + +static void php_swoole_thread_barrier_free_object(zend_object *object) { + BarrierObject *bo = php_swoole_thread_barrier_fetch_object(object); + zend_long resource_id = zend::object_get_long(object, ZEND_STRL("id")); + if (bo->barrier && php_swoole_thread_resource_free(resource_id, bo->barrier)) { + delete bo->barrier; + bo->barrier = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *php_swoole_thread_barrier_create_object(zend_class_entry *ce) { + BarrierObject *bo = (BarrierObject *) zend_object_alloc(sizeof(BarrierObject), ce); + zend_object_std_init(&bo->std, ce); + object_properties_init(&bo->std, ce); + bo->std.handlers = &swoole_thread_barrier_handlers; + return &bo->std; +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_barrier, __construct); +static PHP_METHOD(swoole_thread_barrier, wait); +static PHP_METHOD(swoole_thread_barrier, __wakeup); +SW_EXTERN_C_END + +// clang-format off +static const zend_function_entry swoole_thread_barrier_methods[] = +{ + PHP_ME(swoole_thread_barrier, __construct, arginfo_class_Swoole_Thread_Barrier___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_barrier, wait, arginfo_class_Swoole_Thread_Barrier_wait, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_barrier, __wakeup, arginfo_class_Swoole_Thread_Barrier___wakeup, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_barrier_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_barrier, "Swoole\\Thread\\Barrier", nullptr, swoole_thread_barrier_methods); + zend_declare_property_long(swoole_thread_barrier_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC); + SW_SET_CLASS_CLONEABLE(swoole_thread_barrier, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_barrier, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_barrier, + php_swoole_thread_barrier_create_object, + php_swoole_thread_barrier_free_object, + BarrierObject, + std); +} + +static PHP_METHOD(swoole_thread_barrier, __construct) { + auto bo = php_swoole_thread_barrier_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (bo->barrier != nullptr) { + zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + RETURN_FALSE; + } + + zend_long count; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(count) + ZEND_PARSE_PARAMETERS_END(); + + if (count < 2) { + zend_throw_exception( + swoole_exception_ce, "The parameter $count must be greater than 1", SW_ERROR_INVALID_PARAMS); + RETURN_FALSE; + } + + bo->barrier = new BarrierResource(count); + auto resource_id = php_swoole_thread_resource_insert(bo->barrier); + zend_update_property_long(swoole_thread_barrier_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("id"), resource_id); + RETURN_TRUE; +} + +static PHP_METHOD(swoole_thread_barrier, wait) { + BarrierResource *barrier = php_swoole_thread_barrier_get_and_check_ptr(ZEND_THIS); + if (barrier) { + barrier->wait(); + } +} + +static PHP_METHOD(swoole_thread_barrier, __wakeup) { + auto bo = php_swoole_thread_barrier_fetch_object(Z_OBJ_P(ZEND_THIS)); + zend_long resource_id = zend::object_get_long(ZEND_THIS, ZEND_STRL("id")); + bo->barrier = static_cast(php_swoole_thread_resource_fetch(resource_id)); + if (!bo->barrier) { + zend_throw_exception(swoole_exception_ce, EMSG_NO_RESOURCE, ECODE_NO_RESOURCE); + return; + } +} +#endif diff --git a/ext-src/swoole_thread_lock.cc b/ext-src/swoole_thread_lock.cc index bccd45cd6ac..7ced5d50023 100644 --- a/ext-src/swoole_thread_lock.cc +++ b/ext-src/swoole_thread_lock.cc @@ -37,7 +37,6 @@ using swoole::RWLock; static zend_class_entry *swoole_thread_lock_ce; static zend_object_handlers swoole_thread_lock_handlers; -#ifdef SW_THREAD struct LockResource : public ThreadResource { Lock *lock_; LockResource(int type) : ThreadResource() { @@ -62,7 +61,6 @@ struct LockResource : public ThreadResource { delete lock_; } }; -#endif struct LockObject { LockResource *lock; @@ -113,9 +111,7 @@ static PHP_METHOD(swoole_thread_lock, lock_read); static PHP_METHOD(swoole_thread_lock, trylock_read); static PHP_METHOD(swoole_thread_lock, unlock); static PHP_METHOD(swoole_thread_lock, destroy); -#ifdef SW_THREAD static PHP_METHOD(swoole_thread_lock, __wakeup); -#endif SW_EXTERN_C_END // clang-format off diff --git a/include/swoole.h b/include/swoole.h index 7dac9f5b0f4..3002e8e04cc 100644 --- a/include/swoole.h +++ b/include/swoole.h @@ -176,7 +176,7 @@ typedef unsigned long ulong_t; #define SW_ASSERT(e) #define SW_ASSERT_1BYTE(v) #endif -#define SW_START_SLEEP usleep(100000) // sleep 1s,wait fork and pthread_create +#define SW_START_SLEEP usleep(100000) // sleep 0.1s, wait fork and pthread_create #ifdef SW_THREAD #define SW_THREAD_LOCAL thread_local diff --git a/include/swoole_lock.h b/include/swoole_lock.h index 870ec02a80f..9e15a57d548 100644 --- a/include/swoole_lock.h +++ b/include/swoole_lock.h @@ -105,4 +105,23 @@ class SpinLock : public Lock { int trylock() override; }; #endif + +#if defined(HAVE_PTHREAD_BARRIER) && !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) +#define SW_USE_PTHREAD_BARRIER +#endif + +struct Barrier { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_t barrier_; + pthread_barrierattr_t barrier_attr_; + bool shared_; +#else + sw_atomic_t count_; + sw_atomic_t barrier_; +#endif + void init(bool shared, int count); + void wait(); + void destroy(); +}; + } // namespace swoole diff --git a/include/swoole_server.h b/include/swoole_server.h index e75f0185c86..d845facc018 100644 --- a/include/swoole_server.h +++ b/include/swoole_server.h @@ -394,10 +394,7 @@ struct ServerGS { sw_atomic_t spinlock; -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_t manager_barrier; - pthread_barrierattr_t manager_barrier_attr; -#endif + Barrier manager_barrier; ProcessPool task_workers; ProcessPool event_workers; @@ -858,9 +855,7 @@ class Server { std::shared_ptr> http_index_files = nullptr; std::shared_ptr> http_compression_types = nullptr; -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_t reactor_thread_barrier = {}; -#endif + Barrier reactor_thread_barrier = {}; /** * temporary directory for HTTP uploaded file. diff --git a/src/lock/barrier.cc b/src/lock/barrier.cc new file mode 100644 index 00000000000..042d1cd66fb --- /dev/null +++ b/src/lock/barrier.cc @@ -0,0 +1,63 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.0 of the Apache 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.apache.org/licenses/LICENSE-2.0.html | + | If you did not receive a copy of the Apache2.0 license and are unable| + | to obtain it through the world-wide-web, please send a note to | + | license@swoole.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "swoole.h" +#include "swoole_lock.h" + +namespace swoole { + +#define BARRIER_USEC 10000 + +void Barrier::init(bool shared, int count) { +#ifdef SW_USE_PTHREAD_BARRIER + if (shared) { + pthread_barrierattr_setpshared(&barrier_attr_, PTHREAD_PROCESS_SHARED); + pthread_barrier_init(&barrier_, &barrier_attr_, count); + } else { + pthread_barrier_init(&barrier_, nullptr, count); + } + shared_ = shared; +#else + barrier_ = 0; + count_ = count; +#endif +} + +void Barrier::wait() { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_wait(&barrier_); +#else + sw_atomic_add_fetch(&barrier_, 1); + SW_LOOP { + if (barrier_ == count_) { + break; + } + usleep(BARRIER_USEC); + sw_atomic_memory_barrier(); + } +#endif +} + +void Barrier::destroy() { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_destroy(&barrier_); + if (shared_) { + pthread_barrierattr_destroy(&barrier_attr_); + } +#endif +} + +}; // namespace swoole diff --git a/src/server/manager.cc b/src/server/manager.cc index 8d0f564ca78..0da1b664e81 100644 --- a/src/server/manager.cc +++ b/src/server/manager.cc @@ -192,12 +192,7 @@ void Manager::wait(Server *_server) { int sigid = SIGTERM; procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sigid); #endif - -#if defined(HAVE_PTHREAD_BARRIER) && !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) - pthread_barrier_wait(&_server->gs->manager_barrier); -#else - SW_START_SLEEP; -#endif + _server->gs->manager_barrier.wait(); } if (_server->isset_hook(Server::HOOK_MANAGER_START)) { diff --git a/src/server/master.cc b/src/server/master.cc index f4b175aa610..0976bf99da7 100644 --- a/src/server/master.cc +++ b/src/server/master.cc @@ -434,21 +434,12 @@ int Server::start_master_thread(Reactor *reactor) { return SW_ERR; } -#ifdef HAVE_PTHREAD_BARRIER if (!single_thread) { - pthread_barrier_wait(&reactor_thread_barrier); + reactor_thread_barrier.wait(); } -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) - SW_START_SLEEP; -#else if (is_process_mode()) { - pthread_barrier_wait(&gs->manager_barrier); + gs->manager_barrier.wait(); } -#endif -#else - SW_START_SLEEP; -#endif - gs->master_pid = getpid(); if (isset_hook(HOOK_MASTER_START)) { @@ -835,15 +826,10 @@ int Server::create() { return SW_ERR; } -#ifdef HAVE_PTHREAD_BARRIER if (is_process_mode() || is_thread_mode()) { - pthread_barrier_init(&reactor_thread_barrier, nullptr, reactor_num + 1); -#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) - pthread_barrierattr_setpshared(&gs->manager_barrier_attr, PTHREAD_PROCESS_SHARED); - pthread_barrier_init(&gs->manager_barrier, &gs->manager_barrier_attr, 2); -#endif + reactor_thread_barrier.init(false, reactor_num + 1); + gs->manager_barrier.init(true, 2); } -#endif if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, this); @@ -1060,16 +1046,10 @@ void Server::destroy() { delete l; } } -#ifdef HAVE_PTHREAD_BARRIER if (is_process_mode()) { - pthread_barrier_destroy(&reactor_thread_barrier); -#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) - pthread_barrier_destroy(&gs->manager_barrier); - pthread_barrierattr_destroy(&gs->manager_barrier_attr); -#endif + reactor_thread_barrier.destroy(); + gs->manager_barrier.destroy(); } -#endif - if (is_base_mode()) { destroy_base_factory(); } else if (is_thread_mode()) { diff --git a/src/server/reactor_thread.cc b/src/server/reactor_thread.cc index 881f4ee2bdc..587028557a4 100644 --- a/src/server/reactor_thread.cc +++ b/src/server/reactor_thread.cc @@ -826,11 +826,7 @@ void Server::reactor_thread_main_loop(Server *serv, int reactor_id) { } // wait other thread -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_wait(&serv->reactor_thread_barrier); -#else - SW_START_SLEEP; -#endif + serv->reactor_thread_barrier.wait(); // main loop swoole_event_wait(); if (serv->is_thread_mode()) { diff --git a/tests/swoole_thread/barrier.phpt b/tests/swoole_thread/barrier.phpt new file mode 100644 index 00000000000..1129ab99b5b --- /dev/null +++ b/tests/swoole_thread/barrier.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_thread: barrier +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $barrier = new Barrier(2); + $s = microtime(true); + $thread = new Thread(__FILE__, $barrier); + $barrier->wait(); + Assert::greaterThanEq(microtime(true) - $s, 0.2); + echo "main thread\n"; + $thread->join(); +}; + +$tm->childFunc = function ($barrier) { + echo "child thread\n"; + usleep(200_000); + $barrier->wait(); + exit(0); +}; + +$tm->run(); +?> +--EXPECTF-- +child thread +main thread