From e63515bd4cff149db5aa470c4147c66c7a749859 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Sun, 3 Nov 2024 20:55:54 +0800 Subject: [PATCH] support coroutine feature --- config.m4 | 28 ++++-- ext-src/swoole_lock.cc | 5 + ext-src/swoole_thread_lock.cc | 4 + include/swoole_iouring.h | 10 ++ include/swoole_lock.h | 20 +++- src/coroutine/iouring.cc | 48 +++++++++ src/lock/coroutine_lock.cc | 118 +++++++++++++++++++++++ tests/swoole_lock/coroutine_lock.phpt | 76 +++++++++++++++ tests/swoole_lock/coroutine_trylock.phpt | 44 +++++++++ 9 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 src/lock/coroutine_lock.cc create mode 100644 tests/swoole_lock/coroutine_lock.phpt create mode 100644 tests/swoole_lock/coroutine_trylock.phpt diff --git a/config.m4 b/config.m4 index 49bbe112d6c..3b12e5fce40 100644 --- a/config.m4 +++ b/config.m4 @@ -930,13 +930,6 @@ EOF AC_DEFINE(HAVE_CARES, 1, [have c-ares]) fi - if test "$PHP_IOURING" = "yes"; then - PKG_CHECK_MODULES([URING], [liburing]) - PHP_EVAL_LIBLINE($URING_LIBS, SWOOLE_SHARED_LIBADD) - PHP_EVAL_INCLINE($URING_CFLAGS) - AC_DEFINE(SW_USE_IOURING, 1, [have io_uring]) - fi - AC_SWOOLE_CPU_AFFINITY AC_SWOOLE_HAVE_REUSEPORT AC_SWOOLE_HAVE_FUTEX @@ -959,6 +952,27 @@ EOF dnl Check should we link to librt + if test "$PHP_IOURING" = "yes" && test "$SW_OS" = "LINUX"; then + PKG_CHECK_MODULES([URING], [liburing]) + PHP_EVAL_LIBLINE($URING_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($URING_CFLAGS) + AC_DEFINE(SW_USE_IOURING, 1, [have io_uring]) + + LINUX_VERSION=`uname -r | cut -d '-' -f 1` + LINUX_MAJOR_VERSION=`echo $LINUX_VERSION | cut -d '.' -f 1` + LINUX_MINIO_VERSION=`echo $LINUX_VERSION | cut -d '.' -f 2` + + _PKG_CONFIG(URING_VERSION, [modversion], [liburing]) + IOURING_MAJOR_VERSION=`echo $pkg_cv_URING_VERSION | cut -d '.' -f 1` + IOURING_MINOR_VERSION=`echo $pkg_cv_URING_VERSION | cut -d '.' -f 2` + + if test $IOURING_MAJOR_VERSION > 2 || (test $IOURING_MAJOR_VERSION = 2 && test $IOURING_MINOR_VERSION >= 6); then + if test $LINUX_MAJOR_VERSION > 6 || (test $LINUX_MAJOR_VERSION = 6 && test $LINUX_MAJOR_VERSION >= 7); then + AC_DEFINE(HAVE_IOURING_FUTEX, 1, [have io_uring futex feature]) + fi + fi + fi + if test "$SW_OS" = "LINUX"; then GLIBC_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}') AC_MSG_NOTICE([glibc version: $GLIBC_VERSION]) diff --git a/ext-src/swoole_lock.cc b/ext-src/swoole_lock.cc index 5481317e294..4a1443cdeed 100644 --- a/ext-src/swoole_lock.cc +++ b/ext-src/swoole_lock.cc @@ -30,6 +30,7 @@ using swoole::SpinLock; #ifdef HAVE_RWLOCK using swoole::RWLock; #endif +using swoole::CoroutineLock; static zend_class_entry *swoole_lock_ce; static zend_object_handlers swoole_lock_handlers; @@ -127,6 +128,7 @@ void php_swoole_lock_minit(int module_number) { #ifdef HAVE_SPINLOCK SW_REGISTER_LONG_CONSTANT("SWOOLE_SPINLOCK", Lock::SPIN_LOCK); #endif + SW_REGISTER_LONG_CONSTANT("SWOOLE_COROUTINELOCK", Lock::COROUTINE_LOCK); } static PHP_METHOD(swoole_lock, __construct) { @@ -158,6 +160,9 @@ static PHP_METHOD(swoole_lock, __construct) { case Lock::MUTEX: lock = new Mutex(Mutex::PROCESS_SHARED); break; + case Lock::COROUTINE_LOCK: + lock = new CoroutineLock(); + break; default: zend_throw_exception(swoole_exception_ce, "lock type[%d] is not support", type); RETURN_FALSE; diff --git a/ext-src/swoole_thread_lock.cc b/ext-src/swoole_thread_lock.cc index e52c2bf6775..a86650b306a 100644 --- a/ext-src/swoole_thread_lock.cc +++ b/ext-src/swoole_thread_lock.cc @@ -33,6 +33,7 @@ using swoole::SpinLock; #ifdef HAVE_RWLOCK using swoole::RWLock; #endif +using swoole::CoroutineLock; zend_class_entry *swoole_thread_lock_ce; static zend_object_handlers swoole_thread_lock_handlers; @@ -51,6 +52,9 @@ struct LockResource : public ThreadResource { lock_ = new RWLock(0); break; #endif + case Lock::COROUTINE_LOCK: + lock_ = new CoroutineLock(); + break; case Lock::MUTEX: default: lock_ = new Mutex(0); diff --git a/include/swoole_iouring.h b/include/swoole_iouring.h index 9ff93944f0d..6464209c107 100644 --- a/include/swoole_iouring.h +++ b/include/swoole_iouring.h @@ -22,6 +22,12 @@ #ifdef SW_USE_IOURING #include +#ifdef HAVE_IOURING_FUTEX +#ifndef FUTEX2_SIZE_U32 +#define FUTEX2_SIZE_U32 0x02 +#endif +#endif + using swoole::Coroutine; enum swIouringFlag { @@ -82,6 +88,10 @@ class Iouring { static int rmdir(const char *pathname); static int fsync(int fd); static int fdatasync(int fd); +#ifdef HAVE_IOURING_FUTEX + static int wait_futex(uint32_t *futex); + static int wakeup_futex(uint32_t *futex); +#endif static int callback(Reactor *reactor, Event *event); }; diff --git a/include/swoole_lock.h b/include/swoole_lock.h index 9e15a57d548..dd6a03f0843 100644 --- a/include/swoole_lock.h +++ b/include/swoole_lock.h @@ -31,7 +31,7 @@ class Lock { RW_LOCK = 1, MUTEX = 3, SPIN_LOCK = 5, - ATOMIC_LOCK = 6, + COROUTINE_LOCK = 6, }; Type get_type() { return type_; @@ -106,6 +106,24 @@ class SpinLock : public Lock { }; #endif +class CoroutineLock : public Lock { + private: + long cid = 0; + sw_atomic_t *value = nullptr; + void *coroutine = nullptr; + + int lock_impl(bool blocking = true); + + public: + CoroutineLock(); + ~CoroutineLock(); + int lock_rd() override; + int lock() override; + int unlock() override; + int trylock_rd() override; + int trylock() override; +}; + #if defined(HAVE_PTHREAD_BARRIER) && !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) #define SW_USE_PTHREAD_BARRIER #endif diff --git a/src/coroutine/iouring.cc b/src/coroutine/iouring.cc index 4f86786ad2f..48c846ea244 100644 --- a/src/coroutine/iouring.cc +++ b/src/coroutine/iouring.cc @@ -32,6 +32,10 @@ enum IouringOpcode { SW_IORING_OP_WRITE = IORING_OP_WRITE, SW_IORING_OP_RENAMEAT = IORING_OP_RENAMEAT, SW_IORING_OP_MKDIRAT = IORING_OP_MKDIRAT, +#ifdef HAVE_IOURING_FUTEX + SW_IORING_OP_FUTEX_WAIT = IORING_OP_FUTEX_WAIT, + SW_IORING_OP_FUTEX_WAKE = IORING_OP_FUTEX_WAKE, +#endif SW_IORING_OP_FSTAT = 1000, SW_IORING_OP_LSTAT = 1001, @@ -54,6 +58,9 @@ struct IouringEvent { const char *pathname; const char *pathname2; struct statx *statxbuf; +#ifdef HAVE_IOURING_FUTEX + uint32_t *futex; +#endif }; Iouring::Iouring(Reactor *_reactor) { @@ -206,6 +213,11 @@ static const char *get_opcode_name(IouringOpcode opcode) { return "FSYNC"; case SW_IORING_OP_FDATASYNC: return "FDATASYNC"; +#ifdef HAVE_IOURING_FUTEX + case SW_IORING_OP_FUTEX_WAIT: + case SW_IORING_OP_FUTEX_WAKE: + return "FUTEX"; +#endif default: return "unknown"; } @@ -329,6 +341,26 @@ bool Iouring::dispatch(IouringEvent *event) { sqe->fsync_flags = IORING_FSYNC_DATASYNC; } break; +#ifdef HAVE_IOURING_FUTEX + case SW_IORING_OP_FUTEX_WAIT: + sqe->opcode = SW_IORING_OP_FUTEX_WAIT; + sqe->fd = FUTEX2_SIZE_U32; + sqe->off = 1; + sqe->addr = (unsigned long) event->futex; + sqe->len = 0; + sqe->futex_flags = 0; + sqe->addr3 = FUTEX_BITSET_MATCH_ANY; + break; + case SW_IORING_OP_FUTEX_WAKE: + sqe->opcode = SW_IORING_OP_FUTEX_WAKE; + sqe->fd = FUTEX2_SIZE_U32; + sqe->off = 1; + sqe->addr = (unsigned long) event->futex; + sqe->len = 0; + sqe->futex_flags = 0; + sqe->addr3 = FUTEX_BITSET_MATCH_ANY; + break; +#endif default: abort(); return false; @@ -465,6 +497,22 @@ int Iouring::stat(const char *path, struct stat *statbuf) { return retval; } +#ifdef HAVE_IOURING_FUTEX +int Iouring::wait_futex(uint32_t *futex) { + INIT_EVENT(SW_IORING_OP_FUTEX_WAIT); + event.futex = futex; + + return execute(&event); +} + +int Iouring::wakeup_futex(uint32_t *futex) { + INIT_EVENT(SW_IORING_OP_FUTEX_WAKE); + event.futex = futex; + + return execute(&event); +} +#endif + int Iouring::callback(Reactor *reactor, Event *event) { Iouring *iouring = static_cast(event->socket->object); return iouring->wakeup() ? SW_OK : SW_ERR; diff --git a/src/lock/coroutine_lock.cc b/src/lock/coroutine_lock.cc new file mode 100644 index 00000000000..0c71d9ebc74 --- /dev/null +++ b/src/lock/coroutine_lock.cc @@ -0,0 +1,118 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "swoole.h" + +#ifdef HAVE_IOURING_FUTEX +#include "swoole_iouring.h" +#else +#include "swoole_coroutine_system.h" +using swoole::coroutine::System; +#endif + +#include "swoole_lock.h" + +namespace swoole { +CoroutineLock::CoroutineLock() : Lock() { + type_ = COROUTINE_LOCK; + value = (sw_atomic_t *) sw_mem_pool()->alloc(sizeof(sw_atomic_t)); + *value = 0; +} + +CoroutineLock::~CoroutineLock() { + sw_mem_pool()->free((void *) value); + value = nullptr; +} + +int CoroutineLock::lock() { + return lock_impl(true); +} + +int CoroutineLock::trylock() { + return lock_impl(false); +} + +int CoroutineLock::lock_rd() { + return lock_impl(true); +} + +int CoroutineLock::trylock_rd() { + return lock_impl(false); +} + +int CoroutineLock::unlock() { + Coroutine *current_coroutine = Coroutine::get_current(); + if (current_coroutine == nullptr) { + swoole_warning("The coroutine lock can only be used in a coroutine environment"); + return 1; + } + + if (*value == 0) { + return 0; + } + + *value = 0; + cid = 0; + coroutine = nullptr; +#ifdef HAVE_IOURING_FUTEX + return Iouring::wakeup_futex((uint32_t *) value) >= 0 ? 0 : 1; +#else + return 0; +#endif +} + +int CoroutineLock::lock_impl(bool blocking) { + Coroutine *current_coroutine = Coroutine::get_current(); + if (current_coroutine == nullptr) { + swoole_warning("The coroutine lock can only be used in a coroutine environment"); + return 1; + } + + if (current_coroutine == static_cast(coroutine) && current_coroutine->get_cid() == cid) { + return 0; + } + + int result = 0; +#ifdef HAVE_IOURING_FUTEX + if (!sw_atomic_cmp_set(value, 0, 1)) { + if (!blocking) { + return 1; + } + + result = Iouring::wait_futex((uint32_t *) value); + *value = 1; + } +#else + while (true) { + if (sw_atomic_cmp_set(value, 0, 1)) { + break; + } + + if (!blocking) { + return 1; + } + + if (System::sleep((double) 0.1) != 0) { + return 1; + } + } +#endif + + cid = current_coroutine->get_cid(); + coroutine = (void *) current_coroutine; + return result; +} +} // namespace swoole diff --git a/tests/swoole_lock/coroutine_lock.phpt b/tests/swoole_lock/coroutine_lock.phpt new file mode 100644 index 00000000000..6d407de5d65 --- /dev/null +++ b/tests/swoole_lock/coroutine_lock.phpt @@ -0,0 +1,76 @@ +--TEST-- +swoole_lock: coroutine lock +--FILE-- + 32, + 'iouring_entries' => 20000, + 'iouring_flag' => SWOOLE_IOURING_SQPOLL + ]); +} + +$lock = new Lock(SWOOLE_COROUTINELOCK); +var_dump($lock->lock()); +var_dump($lock->unlock()); + +run(function () use ($argv, $lock) { + $waitGroup = new WaitGroup(); + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + var_dump($lock->lock()); + var_dump($lock->lock()); + sleep(10); + var_dump(1); + var_dump($lock->unlock()); + $waitGroup->done(); + }); + + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + sleep(3); + var_dump($lock->lock()); + var_dump(2); + var_dump($lock->unlock()); + $waitGroup->done(); + }); + + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + sleep(1); + var_dump($lock->lock_read()); + var_dump(3); + var_dump($lock->unlock()); + $waitGroup->done(); + }); + + go(function () use ($waitGroup) { + $waitGroup->add(); + var_dump(5); + $waitGroup->done(); + }); + + $waitGroup->wait(); +}); +?> +--EXPECTF-- +%s +bool(false) +%s +bool(false) +bool(true) +bool(true) +int(5) +int(1) +bool(true) +bool(true) +int(3) +bool(true) +bool(true) +int(2) +bool(true) diff --git a/tests/swoole_lock/coroutine_trylock.phpt b/tests/swoole_lock/coroutine_trylock.phpt new file mode 100644 index 00000000000..042dd29e07c --- /dev/null +++ b/tests/swoole_lock/coroutine_trylock.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_lock: coroutine try lock +--FILE-- + 32, + 'iouring_entries' => 20000, + 'iouring_flag' => SWOOLE_IOURING_SQPOLL + ]); +} + +$lock = new Lock(SWOOLE_COROUTINELOCK); + +run(function () use ($argv, $lock) { + $waitGroup = new WaitGroup(); + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + $lock->lock(); + sleep(2); + var_dump(1); + $lock->unlock(); + $waitGroup->done(); + }); + + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + if (!$lock->trylock() || !$lock->trylock_read()) { + var_dump('lock failed'); + } + $waitGroup->done(); + }); + + $waitGroup->wait(); +}); +?> +--EXPECTF-- +string(11) "lock failed" +int(1)