From 9f3a47020dece06bf8787a2f039a9c3a4a59e73e Mon Sep 17 00:00:00 2001 From: CismonX Date: Sun, 1 Apr 2018 16:35:05 +0800 Subject: [PATCH] archive --- .travis.yml | 26 ++ LICENSE | 21 ++ README.md | 28 ++ config.m4 | 43 +++ src/acceptor.cpp | 171 +++++++++++ src/acceptor.hpp | 52 ++++ src/base.hpp | 58 ++++ src/common.hpp | 197 ++++++++++++ src/future.cpp | 133 ++++++++ src/future.hpp | 95 ++++++ src/generator.hpp | 63 ++++ src/io.hpp | 178 +++++++++++ src/p3.hpp | 130 ++++++++ src/php_asio.cpp | 519 ++++++++++++++++++++++++++++++++ src/php_asio.hpp | 26 ++ src/resolver.cpp | 70 +++++ src/resolver.hpp | 51 ++++ src/service.cpp | 170 +++++++++++ src/service.hpp | 171 +++++++++++ src/signal.cpp | 90 ++++++ src/signal.hpp | 58 ++++ src/socket.cpp | 480 +++++++++++++++++++++++++++++ src/socket.hpp | 188 ++++++++++++ src/strand.cpp | 50 +++ src/strand.hpp | 87 ++++++ src/stream_descriptor.cpp | 86 ++++++ src/stream_descriptor.hpp | 73 +++++ src/timer.cpp | 69 +++++ src/timer.hpp | 52 ++++ src/wrapped_handler.cpp | 14 + src/wrapped_handler.hpp | 48 +++ stubs/Acceptor.php | 44 +++ stubs/DatagramSocket.php | 29 ++ stubs/Future.php | 27 ++ stubs/InetSocket.php | 59 ++++ stubs/IoObject.php | 28 ++ stubs/LocalSocket.php | 49 +++ stubs/Resolver.php | 30 ++ stubs/Service.php | 202 +++++++++++++ stubs/Signal.php | 63 ++++ stubs/Socket.php | 42 +++ stubs/Strand.php | 59 ++++ stubs/StreamDescriptor.php | 75 +++++ stubs/StreamSocket.php | 41 +++ stubs/TcpAcceptor.php | 72 +++++ stubs/TcpResolver.php | 37 +++ stubs/TcpSocket.php | 91 ++++++ stubs/Timer.php | 54 ++++ stubs/UdgSocket.php | 82 +++++ stubs/UdpResolver.php | 37 +++ stubs/UdpSocket.php | 89 ++++++ stubs/UnixAcceptor.php | 69 +++++ stubs/UnixSocket.php | 86 ++++++ stubs/WrappedHandler.php | 33 ++ tests/00-load.phpt | 8 + tests/01-service.phpt | 18 ++ tests/02-timer.phpt | 22 ++ tests/03-signal.phpt | 23 ++ tests/04-socket-bind-open.phpt | 46 +++ tests/05-acceptor.phpt | 63 ++++ tests/06-sendto-recvfrom.phpt | 53 ++++ tests/07-future.phpt | 22 ++ tests/08-assign-read-write.phpt | 41 +++ tests/09-resolver.phpt | 19 ++ tests/10-fork.phpt | 38 +++ tests/11-strand.phpt | 24 ++ 66 files changed, 5272 insertions(+) create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config.m4 create mode 100644 src/acceptor.cpp create mode 100644 src/acceptor.hpp create mode 100644 src/base.hpp create mode 100644 src/common.hpp create mode 100644 src/future.cpp create mode 100644 src/future.hpp create mode 100644 src/generator.hpp create mode 100644 src/io.hpp create mode 100644 src/p3.hpp create mode 100644 src/php_asio.cpp create mode 100644 src/php_asio.hpp create mode 100644 src/resolver.cpp create mode 100644 src/resolver.hpp create mode 100644 src/service.cpp create mode 100644 src/service.hpp create mode 100644 src/signal.cpp create mode 100644 src/signal.hpp create mode 100644 src/socket.cpp create mode 100644 src/socket.hpp create mode 100644 src/strand.cpp create mode 100644 src/strand.hpp create mode 100644 src/stream_descriptor.cpp create mode 100644 src/stream_descriptor.hpp create mode 100644 src/timer.cpp create mode 100644 src/timer.hpp create mode 100644 src/wrapped_handler.cpp create mode 100644 src/wrapped_handler.hpp create mode 100644 stubs/Acceptor.php create mode 100644 stubs/DatagramSocket.php create mode 100644 stubs/Future.php create mode 100644 stubs/InetSocket.php create mode 100644 stubs/IoObject.php create mode 100644 stubs/LocalSocket.php create mode 100644 stubs/Resolver.php create mode 100644 stubs/Service.php create mode 100644 stubs/Signal.php create mode 100644 stubs/Socket.php create mode 100644 stubs/Strand.php create mode 100644 stubs/StreamDescriptor.php create mode 100644 stubs/StreamSocket.php create mode 100644 stubs/TcpAcceptor.php create mode 100644 stubs/TcpResolver.php create mode 100644 stubs/TcpSocket.php create mode 100644 stubs/Timer.php create mode 100644 stubs/UdgSocket.php create mode 100644 stubs/UdpResolver.php create mode 100644 stubs/UdpSocket.php create mode 100644 stubs/UnixAcceptor.php create mode 100644 stubs/UnixSocket.php create mode 100644 stubs/WrappedHandler.php create mode 100644 tests/00-load.phpt create mode 100644 tests/01-service.phpt create mode 100644 tests/02-timer.phpt create mode 100644 tests/03-signal.phpt create mode 100644 tests/04-socket-bind-open.phpt create mode 100644 tests/05-acceptor.phpt create mode 100644 tests/06-sendto-recvfrom.phpt create mode 100644 tests/07-future.phpt create mode 100644 tests/08-assign-read-write.phpt create mode 100644 tests/09-resolver.phpt create mode 100644 tests/10-fork.phpt create mode 100644 tests/11-strand.phpt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7953b2b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +sudo: required +dist: trusty +group: edge + +language: php + +php: + - 7.0 + - 7.1 + - 7.2 + - nightly + +before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo apt-get update -qq + - sudo apt-get install -qq g++-6 + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90 + - sudo apt-get install libboost1.55-all-dev + +script: + - phpize + - ./configure --enable-asio-strand + - make + +after_success: + - make test \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f07be30 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2018 CismonX + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d3a58b --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# php-asio + +[![Travis-CI](https://travis-ci.org/CismonX/php-asio.svg?branch=master)](https://travis-ci.org/CismonX/php-asio) +[![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +## 1. About + +This extension is a PHP wrapper for the Boost.Asio library, which provides a high-performance event-driven model for network I/O. + +Now php-asio is **in development**, do not use it in production. + +Bug reports and pull requests are welcome. + +## 2. Documentation + +See the [Wiki](https://github.com/CismonX/php-asio/wiki) page for documentation. + +Signatures of classes and functions can be found within [stubs/](stubs/) directory, with PHPDoc. + +There are also some test cases in the [tests/](tests/) directory. + +## 3. TODO list + +* Fix memory leak. (Priority: **high**) +* Multi-threading support. (Priority: **medium**) +* Add support for serial ports. (Priority: **low**) +* Add socket `onReadable()` `onWritable()` `onError()` methods (with Boost version 1.66 and above). (Priority: **low**) +* Add support for Windows. (Priority: **low**) diff --git a/config.m4 b/config.m4 new file mode 100644 index 0000000..e13232d --- /dev/null +++ b/config.m4 @@ -0,0 +1,43 @@ +PHP_ARG_ENABLE(asio, for asio support, +[ --enable-asio Enable asio support ]) + +PHP_ARG_ENABLE(asio-coroutine, for coroutine support, +[ --disable-asio-coroutine Disable coroutine support ], yes, no) + +PHP_ARG_ENABLE(asio-strand, for strand support, +[ --enable-asio-strand Enable strand support ], no, no) + +PHP_ARG_ENABLE(asio-null-buffers, for null buffer support, +[ --enable-asio-null-buffers Enable null buffers ], no, no) + +if test "$PHP_ASIO" != "no"; then + PHP_REQUIRE_CXX() + + PHP_ASIO_SRC="src/php_asio.cpp \ + src/service.cpp \ + src/wrapped_handler.cpp \ + src/future.cpp \ + src/strand.cpp \ + src/timer.cpp \ + src/signal.cpp \ + src/resolver.cpp \ + src/socket.cpp \ + src/acceptor.cpp \ + src/stream_descriptor.cpp" + + PHP_NEW_EXTENSION(asio, $PHP_ASIO_SRC, $ext_shared, cli, -std=c++14, yes) + + if test "$PHP_ASIO_COROUTINE" != "no"; then + AC_DEFINE(ENABLE_COROUTINE, 1, [ ]) + fi + if test "$PHP_ASIO_STRAND" != "no"; then + AC_DEFINE(ENABLE_STRAND, 1, [ ]) + fi + if test "$PHP_ASIO_NULL_BUFFERS" != "no"; then + AC_DEFINE(ENABLE_NULL_BUFFERS, 1, [ ]) + fi + + PHP_ADD_LIBRARY(boost_system, 1, ASIO_SHARED_LIBADD) + PHP_ADD_LIBRARY(boost_filesystem, 1, ASIO_SHARED_LIBADD) + PHP_SUBST(ASIO_SHARED_LIBADD) +fi diff --git a/src/acceptor.cpp b/src/acceptor.cpp new file mode 100644 index 0000000..b49b76f --- /dev/null +++ b/src/acceptor.cpp @@ -0,0 +1,171 @@ +/** + * php-asio/acceptor.cpp + * + * @author CismonX + */ + +#include "acceptor.hpp" +#include "future.hpp" +#include "io.hpp" + +namespace asio +{ + template + zval* acceptor::handler(const boost::system::error_code& error, + socket* const socket, zval* callback, zval* argument) + { + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_OBJ(&arguments[1], p3::to_zend_object(socket)); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_OBJ, p3::to_zend_object(socket)); + } + + /* {{{ proto int TcpAcceptor::accept(bool inet6); + * Open socket acceptor. */ + template <> + P3_METHOD(tcp_acceptor, open) + { + zend_bool inet6; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_BOOL(inet6) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + acceptor_.open(inet6 ? tcp::v6() : tcp::v4(), ec); + RETVAL_EC(ec) + } + /* }}} */ + + /* {{{ proto int UnixAcceptor::accept(void); + * Open socket acceptor. */ + template <> + P3_METHOD(unix_acceptor, open) + { + boost::system::error_code ec; + acceptor_.open(unix(), ec); + RETVAL_EC(ec) + } + /* }}} */ + + /* {{{ proto int TcpAcceptor::assign(bool inet6, int|resource fd); + * Assign an existing native socket to the acceptor. */ + template <> + P3_METHOD(tcp_acceptor, assign) + { + PHP_ASIO_INET_ASSIGN(acceptor_, tcp); + } + /* }}} */ + + /* {{{ proto int UnixAcceptor::assign(int|resource fd); + * Assign an existing native socket to the acceptor. */ + template <> + P3_METHOD(unix_acceptor, assign) + { + PHP_ASIO_LOCAL_ASSIGN(acceptor_, unix); + } + /* }}} */ + + /* {{{ proto int TcpAcceptor::bind(string address, int port); + * Bind the acceptor to the specified local endpoint. */ + template <> + P3_METHOD(tcp_acceptor, bind) + { + zend_string* address; + zend_long port_num; + ZEND_PARSE_PARAMETERS_START(2, 2); + Z_PARAM_STR(address) + Z_PARAM_LONG(port_num) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + acceptor_.bind({ boost::asio::ip::address::from_string(ZSTR_VAL(address)), + static_cast(port_num) }, ec); + RETVAL_EC(ec); + } + /* }}} */ + + /* {{{ proto int TcpAcceptor::bind(string path); + * Bind the acceptor to the specified local endpoint. */ + template <> + P3_METHOD(unix_acceptor, bind) + { + zend_string* socket_path; + ZEND_PARSE_PARAMETERS_START(1, 1); + Z_PARAM_STR(socket_path) + ZEND_PARSE_PARAMETERS_END(); + namespace fs = boost::filesystem; + if (fs::status(ZSTR_VAL(socket_path)).type() == fs::socket_file) + fs::remove(ZSTR_VAL(socket_path)); + boost::system::error_code ec; + acceptor_.bind({ ZSTR_VAL(socket_path) }, ec); + RETVAL_EC(ec); + } + /* }}} */ + + /* {{{ proto int Acceptor::listen([int backlog]); + * Put the acceptor into the state where it may accept new connections. */ + template + P3_METHOD(acceptor, listen) + { + zend_long backlog = 0; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(backlog) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + acceptor_.listen(backlog ? static_cast(backlog) : + Protocol::socket::max_connections, ec); + RETVAL_EC(ec); + } + /* }}} */ + + /* {{{ proto int Acceptor::accept([callable callback], [mixed argument]); + * Asynchronously accept a new connection into a socket. */ + template + P3_METHOD(acceptor, accept) + { + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_OBJ_ALLOC(accepted_socket, socket, io_service_); + PHP_ASIO_FUTURE_INIT(); + auto asio_socket = p3::to_object>(accepted_socket); + future->template on_resolve(boost::bind( + &acceptor::handler, this, _1, asio_socket, cb, args)); + acceptor_.async_accept(asio_socket->get_socket(), STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG)); + FUTURE_RETURN(); + } + /* }}} */ + + /* {{{ proto int Acceptor::cancel(void); + * Cancel all asynchronous operations on this acceptor. */ + template + P3_METHOD(acceptor, cancel) + { + boost::system::error_code ec; + RETVAL_EC(acceptor_.cancel(ec)); + } + /* }}} */ + + /* {{{ proto int Acceptor::close(void); + * Stop the acceptor. */ + template + P3_METHOD(acceptor, close) + { + boost::system::error_code ec; + RETVAL_EC(acceptor_.close(ec)); + } + /* }}} */ + + template + zend_class_entry* acceptor::class_entry; + + template + zend_object_handlers acceptor::handlers; + + template class acceptor; + template class acceptor; +} diff --git a/src/acceptor.hpp b/src/acceptor.hpp new file mode 100644 index 0000000..7e92ae8 --- /dev/null +++ b/src/acceptor.hpp @@ -0,0 +1,52 @@ +/** + * php-asio/acceptor.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" +#include "socket.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio stream socket acceptor. + /// Provide TCP services. + template + class acceptor : public base + { + /// Boost.Asio acceptor instance. + typename Protocol::acceptor acceptor_; + + /// Accept handler. + zval* handler(const boost::system::error_code& error, + socket* socket, zval* callback, zval* argument); + + public: + /// Constructor. + explicit acceptor( + boost::asio::io_service& io_service + ) : base(io_service), acceptor_(io_service) {} + + P3_METHOD_DECLARE(open); + + P3_METHOD_DECLARE(assign); + + P3_METHOD_DECLARE(bind); + + P3_METHOD_DECLARE(listen); + + P3_METHOD_DECLARE(accept); + + P3_METHOD_DECLARE(cancel); + + P3_METHOD_DECLARE(close); + + PHP_ASIO_CE_DECLARE(); + }; + + using tcp_acceptor = acceptor; + using unix_acceptor = acceptor; +} diff --git a/src/base.hpp b/src/base.hpp new file mode 100644 index 0000000..3cc51c4 --- /dev/null +++ b/src/base.hpp @@ -0,0 +1,58 @@ +/** + * php-asio/base.hpp + * + * @author CismonX + */ + +#pragma once + +#ifdef ZTS +#include +#define HANDLER_COUNT_TYPE std::atomic +#else +#define HANDLER_COUNT_TYPE unsigned short +#endif // ZTS + +#include "common.hpp" + +namespace asio +{ + /// A base class for all I/O objects. + class base + { + protected: + /// IO service of this object. + boost::asio::io_service& io_service_; + + /// Count of pending async handlers. + HANDLER_COUNT_TYPE handler_count_; + + /// Constructor. + explicit base(boost::asio::io_service& io_service) : io_service_(io_service) {} + + public: + /// Deleted default constructor. + base() = delete; + + /// Default destructor. + ~base() = default; + + /// Deleted copy constructor. + base(const base&) = delete; + + /// Deleted copy assignment operator. + base& operator=(const base&) = delete; + + /// Increment handler count. + unsigned short handler_count_inc() + { + return ++handler_count_; + } + + /// Decrement handler count. + unsigned short handler_count_dec() + { + return --handler_count_; + } + }; +} diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..35af75b --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,197 @@ +/** + * php-asio/common.hpp + * + * @author CismonX + */ + +#pragma once + +#include "p3.hpp" + +#include +#include + +// Compatible with PHP 7.3 +#if PHP_VERSION_ID < 70300 +#define GC_ADDREF(p) ++GC_REFCOUNT(p) +#define GC_DELREF(p) --GC_REFCOUNT(p) +#endif + +#if PHP_VERSION_ID < 70115 || (PHP_VERSION_ID > 70200 && PHP_VERSION_ID < 70203) + +#if PHP_VERSION_ID < 70100 +#define _zend_wrong_parameters_count_error(throw, ...) zend_wrong_paramers_count_error(__VA_ARGS__) +#elif PHP_VERSION_ID < 70200 +#define _zend_wrong_parameters_count_error(throw, ...) zend_wrong_parameters_count_error(__VA_ARGS__) +#else +#define _zend_wrong_parameters_count_error(...) zend_wrong_parameters_count_error(__VA_ARGS__) +#endif + +// See https://externals.io/message/101364 for details. +#undef ZEND_PARSE_PARAMETERS_START_EX +#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \ + const int _flags = (flags); \ + int _min_num_args = (min_num_args); \ + int _max_num_args = (max_num_args); \ + int _num_args = EX_NUM_ARGS(); \ + int _i; \ + zval *_real_arg, *_arg = NULL; \ + zend_expected_type _expected_type = Z_EXPECTED_LONG; \ + char *_error = NULL; \ + zend_bool _dummy; \ + zend_bool _optional = 0; \ + int error_code = ZPP_ERROR_OK; \ + ((void)_i); \ + ((void)_real_arg); \ + ((void)_arg); \ + ((void)_expected_type); \ + ((void)_error); \ + ((void)_dummy); \ + ((void)_optional); \ + do { \ + if (UNEXPECTED(_num_args < _min_num_args) || \ + (UNEXPECTED(_num_args > _max_num_args) && \ + EXPECTED(_max_num_args >= 0))) { \ + if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \ + _zend_wrong_parameters_count_error(_flags & ZEND_PARSE_PARAMS_THROW, \ + _num_args, _min_num_args, _max_num_args); \ + } \ + error_code = ZPP_ERROR_FAILURE; \ + break; \ + } \ + _i = 0; \ + _real_arg = ZEND_CALL_ARG(execute_data, 0); + +#endif + +#define PHP_ASIO_OBJ_ALLOC(obj, type, arg) \ + auto obj = p3::alloc_object(type::class_entry, [this](type* ptr) { \ + new(ptr) type(arg); \ + }) + +#define PHP_ASIO_OBJ_DTOR(obj) GC_DELREF(p3::to_zend_object(obj)) + +#define ZVAL_ALLOC(name) name = static_cast(emalloc(sizeof(zval))) +#define ZVAL_PTR_INIT(name) auto ZVAL_ALLOC(name) +#define ZVAL_INIT(name) zval name = {{ 0 }} + +#define RETVAL_EC(ec) RETVAL_LONG(static_cast((ec).value())) + +#define PHP_ASIO_ERROR(type, msg) php_error_docref(nullptr, type, msg) + +#define PHP_ASIO_CE_DECLARE() \ + static zend_class_entry* class_entry; \ + static zend_object_handlers handlers +#define PHP_ASIO_CE_DEFINE(type) \ + zend_class_entry* type::class_entry; \ + zend_object_handlers type::handlers + + // Handlers with one argument is treated as ones with two arguments. +#define NOARG int +#define ASYNC_HANDLER_SINGLE_ARG \ + std::function(boost::bind( \ + &future::resolve, NOARG>, \ + future, boost::asio::placeholders::error, 0)) +#define ASYNC_HANDLER_DOUBLE_ARG(obj_type) \ + std::function(boost::bind( \ + &future::resolve, obj_type>, \ + future, boost::asio::placeholders::error, _2)) + +// If you don't need coroutines, you can turn it off for better performance. +#ifdef ENABLE_COROUTINE +#define CORO_REGISTER(value) future::coroutine(value) +#define FUTURE_INIT() \ + zend_object* obj; \ + auto future = future::add(this, obj); +#define FUTURE_RETURN() RETVAL_OBJ(obj) +#define INIT_RETVAL() \ + ZVAL_PTR_INIT(retval); \ + ZVAL_NULL(retval) +#define PASS_RETVAL retval +#else +#define CORO_REGISTER(value) +#define FUTURE_INIT() auto future = future::add(this) +#define FUTURE_RETURN() +#define INIT_RETVAL() ZVAL_INIT(retval) +#define PASS_RETVAL &retval +#endif // ENABLE_COROUTINE + +#define CORO_RETURN_NULL() \ + ZVAL_PTR_INIT(retval); \ + ZVAL_NULL(retval); \ + return retval +#define CORO_RETURN(type, value) \ + ZVAL_PTR_INIT(retval); \ + type(retval, value); \ + return retval + +// If you don't need multi-threading support for I/O objects, you can disable Strand for better performance. +#if defined(ENABLE_STRAND) && !defined(ZTS) +#undef ENABLE_STRAND +#endif +#ifdef ENABLE_STRAND +#define STRAND_UNWRAP() \ + callback = future->handle_strand(callback); \ + if (future->get_strand()) \ + ZVAL_COPY_VALUE(cb, callback); \ + else +#define STRAND_RESOLVE(arg) future->get_strand() ? future->get_strand()->wrap(arg) : arg +#else +#define STRAND_UNWRAP() +#define STRAND_RESOLVE(arg) arg +#endif // ENABLE_STRAND + +#define PHP_ASIO_INVOKE_CALLBACK_START(argc) \ + const auto _argc = argc; \ + if (callback && zend_is_callable(callback, 0, nullptr)) { \ + zval arguments[_argc] = {{{ 0 }}}; \ + ZVAL_OBJ(&arguments[0], p3::to_zend_object(this)); + +#define PHP_ASIO_INVOKE_CALLBACK() \ + ZVAL_LONG(&arguments[_argc - 2], static_cast(error.value())); \ + if (argument) \ + ZVAL_COPY(&arguments[_argc - 1], argument); \ + else \ + ZVAL_NULL(&arguments[_argc - 1]); \ + INIT_RETVAL(); \ + call_user_function(CG(function_table), nullptr, callback, PASS_RETVAL, _argc, arguments) + +#define PHP_ASIO_INVOKE_CALLBACK_END() \ + CORO_REGISTER(retval); \ + zval_ptr_dtor(callback); \ + efree(callback); \ + } \ + if (argument) { \ + zval_ptr_dtor(argument); \ + efree(argument); \ + } + +#define PHP_ASIO_INC_HANDLER_COUNT() \ + if (handler_count_inc() == 1) \ + GC_ADDREF(p3::to_zend_object(this)) + +#define PHP_ASIO_DEC_HANDLER_COUNT() \ + if (handler_count_dec() == 0) \ + PHP_ASIO_OBJ_DTOR(this) + +// To ensure the callback and the extra arg is still alive when async operation resolves, +// We shall allocate new memory on the heap. +#define PHP_ASIO_FUTURE_INIT() \ + PHP_ASIO_INC_HANDLER_COUNT(); \ + FUTURE_INIT(); \ + zval* cb = nullptr; \ + if (callback) { \ + ZVAL_ALLOC(cb); \ + STRAND_UNWRAP() \ + ZVAL_COPY(cb, callback); \ + } \ + zval* args = nullptr; \ + if (argument) { \ + ZVAL_ALLOC(args); \ + ZVAL_COPY(args, argument); \ + } + +using boost::asio::ip::tcp; +using boost::asio::ip::udp; +using unix = boost::asio::local::stream_protocol; +using udg = boost::asio::local::datagram_protocol; diff --git a/src/future.cpp b/src/future.cpp new file mode 100644 index 0000000..bc44a6d --- /dev/null +++ b/src/future.cpp @@ -0,0 +1,133 @@ +/** + * php-asio/future.cpp + * + * @author CismonX + */ + +#include "future.hpp" +#include "generator.hpp" +#include "timer.hpp" +#include "signal.hpp" +#include "resolver.hpp" +#include "socket.hpp" +#include "acceptor.hpp" +#include "stream_descriptor.hpp" + +namespace asio +{ + future* future::add( + void* io_object +#ifdef ENABLE_COROUTINE + , zend_object*& obj) + { + obj = p3::alloc_object(class_entry, + [io_object](future* ptr) { + new(ptr) future(io_object); + }); + GC_ADDREF(obj); + return p3::to_object(obj); +#else + ) { + return new future(io_object); +#endif // ENABLE_COROUTINE + } + + template + void future::on_resolve(const ASYNC_CALLBACK(T)&& callback) + { + callback_ = new ASYNC_CALLBACK(T)(std::move(callback)); + } + + template + void future::resolve(const boost::system::error_code& ec, T arg) + { + auto callback = static_cast(callback_); + send_ = (*callback)(ec, arg); +#ifdef ENABLE_COROUTINE + if (yield_) { + last_error_ = static_cast(ec.value()); + generator_send(reinterpret_cast(Z_OBJ_P(generator_)), send_); + coroutine(generator_); + } +#endif // ENABLE_COROUTINE + zval_ptr_dtor(send_); + efree(send_); + delete callback; + auto io_object = static_cast(io_object_); + if (io_object->handler_count_dec() == 0) + PHP_ASIO_OBJ_DTOR(io_object); +#ifdef ENABLE_STRAND + if (strand_ && strand_->handler_count_dec() == 0) + PHP_ASIO_OBJ_DTOR(strand_); +#endif // ENABLE_STRAND +#ifdef ENABLE_COROUTINE + PHP_ASIO_OBJ_DTOR(this); +#else + delete this; +#endif // ENABLE_COROUTINE + } + +#ifdef ENABLE_STRAND + zval* future::handle_strand(zval* callable) + { + if (callable && Z_TYPE_P(callable) == IS_OBJECT && + instanceof_function(Z_OBJCE_P(callable), wrapped_handler::class_entry)) { + const auto wrapped_hander = p3::to_object(callable); + strand_ = wrapped_hander->strand_; + return wrapped_hander->callback_; + } + return callable; + } +#endif // ENABLE_STRAND + +#ifdef ENABLE_COROUTINE + void future::coroutine(zval* value) + { + if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), zend_ce_generator)) { + const auto generator = reinterpret_cast(Z_OBJ_P(value)); + if (generator_valid(generator)) { + const auto ret = generator_current(generator); + if (ret && instanceof_function(Z_OBJCE_P(ret), class_entry)) { + const auto future = p3::to_object(ret); + future->generator_ = value; + future->yield_ = true; + return; + } + PHP_ASIO_ERROR(E_WARNING, "Invalid yield value. Future expected."); + } + } + zval_ptr_dtor(value); + efree(value); + } + + P3_METHOD(future, lastError) + { + RETVAL_LONG(last_error_) + } + + thread_local int64_t future::last_error_ = 0; + + PHP_ASIO_CE_DEFINE(future); +#endif // ENABLE_COROUTINE + + template void future::on_resolve(const ASYNC_CALLBACK(int)&&); + template void future::on_resolve(const ASYNC_CALLBACK(size_t)&&); + template void future::on_resolve(const ASYNC_CALLBACK(tcp::resolver::iterator)&&); + template void future::on_resolve(const ASYNC_CALLBACK(udp::resolver::iterator)&&); + + template void future::resolve(const boost::system::error_code&, int); + template void future::resolve(const boost::system::error_code&, int); + template void future::resolve>( + const boost::system::error_code&, tcp::resolver::iterator); + template void future::resolve>( + const boost::system::error_code&, udp::resolver::iterator); + template void future::resolve>(const boost::system::error_code&, int); + template void future::resolve>(const boost::system::error_code&, size_t); + template void future::resolve>(const boost::system::error_code&, int); + template void future::resolve>(const boost::system::error_code&, size_t); + template void future::resolve>(const boost::system::error_code&, size_t); + template void future::resolve>(const boost::system::error_code&, size_t); + template void future::resolve>(const boost::system::error_code&, int); + template void future::resolve>(const boost::system::error_code&, int); + template void future::resolve(const boost::system::error_code&, size_t); +} diff --git a/src/future.hpp b/src/future.hpp new file mode 100644 index 0000000..d366bde --- /dev/null +++ b/src/future.hpp @@ -0,0 +1,95 @@ +/** + * php-asio/future.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "wrapped_handler.hpp" + +#define ASYNC_CALLBACK(type) std::function + +namespace asio +{ + /// Class Future. + /// When an asynchronous operation completes, its Future will be resolved. + /// And the corresponding coroutine will resume (if Future was yielded by a Generator). + class future + { + /// Handler callback of the async operation. + void* callback_ = nullptr; + + /// Pointer to the I/O object which created this Future. + void* io_object_; + +#ifdef ENABLE_COROUTINE + /// Last error code emitted by yielded async operations of this thread. + static thread_local int64_t last_error_; + + /// Generator instance which yielded this Future. + zval* generator_ = nullptr; + + /// Whether this future is yielded by a Generator. + bool yield_ = false; +#endif // ENABLE_COROUTINE + + /// Value which will be sent back to the Generator. + zval* send_ = nullptr; + +#ifdef ENABLE_STRAND + /// Pointer to Strand which wrapped this Future. + strand* strand_ = nullptr; +#endif // ENABLE_STRAND + + /// Constructor. + explicit future(void* io_object) : io_object_(io_object) {} + + public: + /// Create a new Future instance. + static future* add( + void* io_object +#ifdef ENABLE_COROUTINE + , zend_object*& obj +#endif // ENABLE_COROUTINE + ); + + /// Deleted default constructor. + explicit future() = delete; + + /// Deleted copy constructor. + future(const future&) = delete; + + /// Deleted copy assignment operator. + future& operator=(const future&) = delete; + + /// Set future resolver callback. + template + void on_resolve(const ASYNC_CALLBACK(T)&& callback); + + /// Resolve the Future upon operation completion. + template + void resolve(const boost::system::error_code& ec, T arg); + +#ifdef ENABLE_STRAND + zval* handle_strand(zval* callable); + + /// Get the pointer to the strand which wrapped this Future. + boost::asio::strand* get_strand() const + { + return strand_ ? strand_->implmentation() : nullptr; + } +#endif // ENABLE_STRAND + +#ifdef ENABLE_COROUTINE + /// Attempt to start/resume a coroutine with a PHP Generator. + static void coroutine(zval* value); + + /// Get last error emitted by handler callback within yielded Future. + static P3_METHOD_DECLARE(lastError); + + PHP_ASIO_CE_DECLARE(); +#endif // ENABLE_COROUTINE + }; +} diff --git a/src/generator.hpp b/src/generator.hpp new file mode 100644 index 0000000..17b637f --- /dev/null +++ b/src/generator.hpp @@ -0,0 +1,63 @@ +/** + * php-asio/generator.hpp + * + * Several functions copied from zend_generators.c, + * which provides PHP's generator functionalities. + * Used by Future when coroutine is enabled. + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" + +#include + +#ifdef ENABLE_COROUTINE +namespace asio +{ + /// Ensure that the generator is initialized. + inline void zend_generator_ensure_initialized(zend_generator* generator) + { + if (UNEXPECTED(Z_TYPE(generator->value) == IS_UNDEF) && + EXPECTED(generator->execute_data) && + EXPECTED(generator->node.parent == nullptr)) { + generator->flags |= ZEND_GENERATOR_DO_INIT; + zend_generator_resume(generator); + generator->flags &= ~ZEND_GENERATOR_DO_INIT; + generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD; + } + } + + /// Check whether the generator is valid. + inline bool generator_valid(zend_generator* generator) + { + zend_generator_ensure_initialized(generator); + zend_generator_get_current(generator); + return EXPECTED(generator->execute_data != nullptr); + } + + /// Get current yield value of the generator. + inline zval* generator_current(zend_generator* generator) + { + zend_generator_ensure_initialized(generator); + const auto root = zend_generator_get_current(generator); + if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->value) != IS_UNDEF)) + return &root->value; + return nullptr; + } + + /// Send a value to the generator. + inline void generator_send(zend_generator* generator, zval* value) + { + zend_generator_ensure_initialized(generator); + if (UNEXPECTED(!generator->execute_data)) + return; + const auto root = zend_generator_get_current(generator); + if (root->send_target) + ZVAL_COPY(root->send_target, value); + zend_generator_resume(generator); + } +} +#endif // ENABLE_COROUTINE diff --git a/src/io.hpp b/src/io.hpp new file mode 100644 index 0000000..1f0b552 --- /dev/null +++ b/src/io.hpp @@ -0,0 +1,178 @@ +/** + * php-asio/io.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" + +#define PHP_ASIO_INET_ASSIGN(obj, p) \ + zend_bool inet6; \ + zval* fd; \ + ZEND_PARSE_PARAMETERS_START(2, 2) \ + Z_PARAM_BOOL(inet6) \ + Z_PARAM_ZVAL(fd) \ + ZEND_PARSE_PARAMETERS_END(); \ + boost::system::error_code ec; \ + const auto protocol = inet6 ? p::v6() : p::v4(); \ + if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG)) \ + (obj).assign(protocol, Z_LVAL_P(fd), ec); \ + else \ + (obj).assign(protocol, resource_to_fd(fd), ec); \ + RETVAL_EC(ec) + +#define PHP_ASIO_LOCAL_ASSIGN(obj, p) \ + zval* fd; \ + ZEND_PARSE_PARAMETERS_START(1, 1) \ + Z_PARAM_ZVAL(fd) \ + ZEND_PARSE_PARAMETERS_END(); \ + boost::system::error_code ec; \ + if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG)) \ + (obj).assign(p(), Z_LVAL_P(fd), ec); \ + else \ + (obj).assign(p(), resource_to_fd(fd), ec); \ + RETVAL_EC(ec) + +#if defined(ENABLE_NULL_BUFFERS) && BOOST_VERSION >= 106600 + // Null buffers are deprecated as of Boost 1.66. + // Method `async_wait()` on sockets and stream descriptors is preferred. +#undef ENABLE_NULL_BUFFERS +#endif + +#ifdef ENABLE_NULL_BUFFERS +#define PHP_ASIO_BUFFER_LEN_VALIDATE() \ + if (UNEXPECTED(length < 0)) { \ + PHP_ASIO_ERROR(E_WARNING, "Non-negative integer expected."); \ + RETURN_NULL(); \ + } +#define PHP_ASIO_EMPTY_READ_BUFFER length == 0 ? ZSTR_EMPTY_ALLOC() : +#define PHP_ASIO_ON_READABLE(obj) \ + if (length == 0) \ + if (read_some) \ + (obj).async_read_some(boost::asio::null_buffers(), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else \ + async_read(obj, boost::asio::null_buffers(), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else +#define PHP_ASIO_EMPTY_WRITE_BUFFER ZSTR_LEN(data) == 0 ? nullptr : +#define PHP_ASIO_ON_WRITABLE(obj) \ + if (ZSTR_LEN(data) == 0) \ + if (write_some) \ + (obj).async_write_some(boost::asio::null_buffers(), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else \ + async_write(obj, boost::asio::null_buffers(), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else +#else +#define PHP_ASIO_BUFFER_LEN_VALIDATE() \ + if (UNEXPECTED(length <= 0)) { \ + PHP_ASIO_ERROR(E_WARNING, "Positive integer expected."); \ + RETURN_NULL(); \ + } +#define PHP_ASIO_EMPTY_READ_BUFFER +#define PHP_ASIO_ON_READABLE(obj) +#define PHP_ASIO_EMPTY_WRITE_BUFFER +#define PHP_ASIO_ON_WRITABLE(obj) +#endif // ENABLE_NULL_BUFFERS + +#define PHP_ASIO_READ(type, obj) \ + zend_long length; \ + zend_bool read_some = 1; \ + zval* callback = nullptr; \ + zval* argument = nullptr; \ + ZEND_PARSE_PARAMETERS_START(1, 4) \ + Z_PARAM_LONG(length) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_BOOL(read_some) \ + Z_PARAM_ZVAL(callback) \ + Z_PARAM_ZVAL(argument) \ + ZEND_PARSE_PARAMETERS_END(); \ + PHP_ASIO_BUFFER_LEN_VALIDATE(); \ + auto buffer_container = PHP_ASIO_EMPTY_READ_BUFFER \ + zend_string_alloc(static_cast(length), 0); \ + PHP_ASIO_FUTURE_INIT(); \ + future->template on_resolve(boost::bind(&type::read_handler, \ + this, _1, _2, buffer_container, cb, args)); \ + PHP_ASIO_ON_READABLE(obj) \ + if (read_some) \ + (obj).async_read_some(mutable_buffer(buffer_container), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else \ + async_read(obj, mutable_buffer(buffer_container), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + FUTURE_RETURN() + +#define PHP_ASIO_WRITE(type, obj) \ + zend_string* data; \ + zend_bool write_some = 0; \ + zval* callback = nullptr; \ + zval* argument = nullptr; \ + ZEND_PARSE_PARAMETERS_START(1, 4) \ + Z_PARAM_STR(data) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_BOOL(write_some) \ + Z_PARAM_ZVAL(callback) \ + Z_PARAM_ZVAL(argument) \ + ZEND_PARSE_PARAMETERS_END(); \ + auto buffer_container = PHP_ASIO_EMPTY_WRITE_BUFFER \ + zend_string_copy(data); \ + PHP_ASIO_FUTURE_INIT(); \ + future->template on_resolve(boost::bind(&type::write_handler, \ + this, _1, _2, buffer_container, cb, args)); \ + PHP_ASIO_ON_WRITABLE(obj) \ + if (write_some) \ + (obj).async_write_some(mutable_buffer(buffer_container), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + else \ + async_write(obj, mutable_buffer(buffer_container), \ + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); \ + FUTURE_RETURN() + +namespace asio +{ + /// Wrap a zend string into const buffer. + inline auto const_buffer(const zend_string* str) + { + return boost::asio::const_buffers_1(boost::asio::const_buffer( + ZSTR_LEN(str) ? ZSTR_VAL(str) : nullptr, ZSTR_LEN(str) + )); + } + + /// Wrap a zend string into mutable buffer. + inline auto mutable_buffer(zend_string* str) + { + return boost::asio::mutable_buffers_1(boost::asio::mutable_buffer( + ZSTR_LEN(str) ? ZSTR_VAL(str) : nullptr, ZSTR_LEN(str) + )); + } + + /// Extract a valid poll fd from a PHP resource. + inline int resource_to_fd(zval* resource) + { + // The following code is copied from php-uv. + // See https://github.com/bwoebi/php-uv/blob/master/php_uv.c#L401 + if (UNEXPECTED(Z_TYPE_P(resource) != IS_RESOURCE)) + return -1; + const auto stream = static_cast( + zend_fetch_resource_ex(resource, nullptr, php_file_le_stream())); + if (stream == nullptr) + return -1; + if (stream->wrapper && !strcmp(stream->wrapper->wops->label, "PHP") && + (!stream->orig_path || + strncmp(stream->orig_path, "php://std", sizeof "php://std" - 1) && + strncmp(stream->orig_path, "php://fd", sizeof "php://fd" - 1))) + return -1; + auto fd = -1; + if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + reinterpret_cast(&fd), 1) == SUCCESS && fd >= 0) { + if (stream->wrapper && !strcmp(stream->wrapper->wops->label, "plainfile")) + return -1; + return fd; + } + return -1; + } +} diff --git a/src/p3.hpp b/src/p3.hpp new file mode 100644 index 0000000..76a166a --- /dev/null +++ b/src/p3.hpp @@ -0,0 +1,130 @@ +/** + * php-asio/p3.hpp + * + * This header is a simple helper for wrapping C++ classes, + * which is borrowed from https://github.com/phplang/p3. + * The casting/cloning/comparing functionalities are removed, + * because they are not needed by php-asio. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include + +#include +#include + +#define P3_METHOD_DECLARE(name) \ + void zim_##name(INTERNAL_FUNCTION_PARAMETERS) + +#define P3_METHOD(cls, name) \ + void cls::zim_##name(INTERNAL_FUNCTION_PARAMETERS) + +#define P3_ME(cls, name, meth, arginfo, flags) \ + ZEND_FENTRY(name, [](INTERNAL_FUNCTION_PARAMETERS) { \ + ::p3::to_object(getThis())->zim_##meth(INTERNAL_FUNCTION_PARAM_PASSTHRU); \ + }, arginfo, flags) + +#define P3_ME_D(cls, meth, arginfo, flags) \ + P3_ME(cls, meth, meth, arginfo, flags) + +#define P3_STATIC_ME(cls, meth, arginfo, flags) \ + ZEND_FENTRY(meth, &cls::zim_##meth, arginfo, flags | ZEND_ACC_STATIC) + +#define P3_ABSTRACT_ME(name, arginfo) \ + PHP_ABSTRACT_ME("", name, arginfo) + +namespace p3 { + /// Native object to Zend object. + template + zend_object* to_zend_object(T* obj) + { + return reinterpret_cast(obj + 1); + } + + /// Zend object to native object. + template + T* to_object(zend_object* obj) + { + return reinterpret_cast(obj) - 1; + } + + /// Zval to native object. + template + T* to_object(zval* obj) + { + return reinterpret_cast(Z_OBJ_P(obj)) - 1; + } + + /// Allocate new object. + template + zend_object* alloc_object(zend_class_entry* ce, InitFunc init) + { + auto ptr = reinterpret_cast(ecalloc(1, sizeof(T) + + sizeof(zend_object) + zend_object_properties_size(ce))); + init(ptr); + auto zobj = to_zend_object(ptr); + zend_object_std_init(zobj, ce); + zobj->handlers = &T::handlers; + return zobj; + } + + /// Allocate new object with default constructor. + template + typename std::enable_if::value, zend_object*>::type + create_object(zend_class_entry* ce) + { + return alloc_object(ce, [](T* ptr) { + new(ptr) T(); + }); + } + + template + typename std::enable_if::value, zend_object*>::type + create_object(zend_class_entry* ce) + { + assert(false); + return nullptr; + } + + /// Destroy an object. + template + void dtor_object(zend_object *obj) + { + zend_object_std_dtor(obj); + to_object(obj)->~T(); + } + + /// Fail to create object if there's no default constructor. + inline zend_object* create_object_fail(zend_class_entry* ce) { + php_error_docref(nullptr, E_ERROR, + "%s should not be directly instantiated.", ZSTR_VAL(ce->name)); + return zend_objects_new(ce); + } + + template + zend_class_entry* class_init(const char* name, const zend_function_entry* methods) + { + zend_class_entry ce; + INIT_CLASS_ENTRY_EX(ce, name, strlen(name), methods); + T::class_entry = zend_register_internal_class(&ce); + T::class_entry->create_object = + std::is_constructible::value ? create_object : create_object_fail; + memcpy(&T::handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + T::handlers.offset = sizeof(T); + T::handlers.free_obj = dtor_object; + T::handlers.clone_obj = nullptr; + return T::class_entry; + } + + inline zend_class_entry* interface_init(const char* name, const zend_function_entry* methods) + { + zend_class_entry ce; + INIT_CLASS_ENTRY_EX(ce, name, strlen(name), methods); + return zend_register_internal_interface(&ce); + } +} diff --git a/src/php_asio.cpp b/src/php_asio.cpp new file mode 100644 index 0000000..a089ded --- /dev/null +++ b/src/php_asio.cpp @@ -0,0 +1,519 @@ +/** + * php-asio/php_asio.cpp + * + * @author CismonX + */ + +#include +#include + +#include "php_asio.hpp" +#include "p3.hpp" +#include "service.hpp" +#include "wrapped_handler.hpp" + +/* {{{ arg_info */ + +ZEND_BEGIN_ARG_INFO(service_run_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(service_run_one_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(service_poll_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(service_poll_one_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(service_dispatch_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +#ifdef ENABLE_STRAND +ZEND_BEGIN_ARG_INFO(strand_dispatch_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() +#endif // ENABLE_STRAND + +ZEND_BEGIN_ARG_INFO(strand_wrap_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(timer_expires_from_now_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, duration, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(timer_expires_at_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, timestamp, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(timer_wait_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(signal_add_arginfo, 0) + ZEND_ARG_VARIADIC_INFO(0, sig_num) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(signal_remove_arginfo, 0) + ZEND_ARG_VARIADIC_INFO(0, sig_num) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(signal_wait_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(resolver_resolve_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, host, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, service, IS_STRING, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(socket_available_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(socket_at_mark_arginfo, 0) + ZEND_ARG_INFO(1, ec) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(inet_socket_open_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, inet6, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(inet_socket_bind_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(inet_socket_assign_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, inet6, _IS_BOOL, 0) + ZEND_ARG_INFO(0, native_handle) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(local_socket_assign_arginfo, 0) + ZEND_ARG_INFO(0, native_handle) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(stream_socket_read_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, read_some, _IS_BOOL, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(stream_socket_write_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, write_some, _IS_BOOL, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(local_socket_bind_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(datagram_socket_recv_from_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(tcp_socket_connect_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(unix_socket_connect_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(udp_socket_send_to_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, address, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(udg_socket_send_to_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, path, IS_STRING, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(acceptor_listen_arginfo, 0) + ZEND_ARG_TYPE_INFO(0, backlog, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(acceptor_accept_arginfo, 0) + ZEND_ARG_CALLABLE_INFO(0, callback, 0) + ZEND_ARG_INFO(0, argument) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(stream_descriptor_assign_arginfo, 0) + ZEND_ARG_INFO(0, native_handle) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ function_entry */ + +static zend_function_entry service_methods[] = { + P3_ME_D(asio::service, addTimer, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addSignal, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addTcpResolver, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addUdpResolver, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addTcpSocket, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addUdpSocket, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addUnixSocket, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addUdgSocket, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addTcpAcceptor, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addUnixAcceptor, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, addStreamDescriptor, nullptr, ZEND_ACC_PUBLIC) +#ifdef ENABLE_STRAND + P3_ME_D(asio::service, addStrand, nullptr, ZEND_ACC_PUBLIC) +#endif // ENABLE_STRAND + P3_ME_D(asio::service, run, service_run_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, runOne, service_run_one_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, poll, service_poll_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, pollOne, service_poll_one_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, stop, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, reset, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, stopped, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, post, service_dispatch_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, dispatch, service_dispatch_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, forkPrepare, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, forkParent, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::service, forkChild, nullptr, ZEND_ACC_PUBLIC) +#ifdef ENABLE_COROUTINE + P3_STATIC_ME(asio::future, lastError, nullptr, ZEND_ACC_PUBLIC) +#endif // ENABLE_COROUTINE + PHP_FE_END +}; + +#ifdef ENABLE_STRAND +static zend_function_entry strand_methods[] = { + P3_ME_D(asio::strand, dispatch, strand_dispatch_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::strand, post, strand_dispatch_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::strand, runningInThisThread, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::strand, wrap, strand_wrap_arginfo, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry wrapped_handler_methods[] = { + P3_ME_D(asio::wrapped_handler, __invoke, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +#endif // ENABLE_STRAND + +static zend_function_entry io_object_method[] = { + P3_ABSTRACT_ME(cancel, nullptr) + P3_ABSTRACT_ME(destroy, nullptr) + PHP_FE_END +}; + +static zend_function_entry timer_methods[] = { + P3_ME_D(asio::timer, expiresFromNow, timer_expires_from_now_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::timer, expiresAt, timer_expires_at_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::timer, wait, timer_wait_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::timer, cancel, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry signal_methods[] = { + P3_ME_D(asio::signal, add, signal_add_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::signal, remove, signal_remove_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::signal, wait, signal_wait_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::signal, clear, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::signal, cancel, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry resolver_methods[] = { + P3_ABSTRACT_ME(resolve, resolver_resolve_arginfo) + P3_ABSTRACT_ME(cancel, nullptr) + PHP_FE_END +}; + +static zend_function_entry tcp_resolver_methods[] = { + P3_ME_D(asio::tcp_resolver, resolve, resolver_resolve_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_resolver, cancel, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry udp_resolver_methods[] = { + P3_ME_D(asio::udp_resolver, resolve, resolver_resolve_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_resolver, cancel, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry socket_methods[] = { + P3_ABSTRACT_ME(available, socket_available_arginfo) + P3_ABSTRACT_ME(atMark, socket_at_mark_arginfo) + P3_ABSTRACT_ME(close, nullptr) + PHP_FE_END +}; + +static zend_function_entry inet_socket_methods[] = { + P3_ABSTRACT_ME(open, inet_socket_open_arginfo) + P3_ABSTRACT_ME(assign, inet_socket_assign_arginfo) + P3_ABSTRACT_ME(bind, inet_socket_bind_arginfo) + P3_ABSTRACT_ME(remoteAddr, nullptr) + P3_ABSTRACT_ME(remotePort, nullptr) + PHP_FE_END +}; + +static zend_function_entry local_socket_methods[] = { + P3_ABSTRACT_ME(open, nullptr) + P3_ABSTRACT_ME(assign, local_socket_assign_arginfo) + P3_ABSTRACT_ME(bind, local_socket_bind_arginfo) + P3_ABSTRACT_ME(remotePath, nullptr) + PHP_FE_END +}; + +static zend_function_entry stream_socket_methods[] = { + P3_ABSTRACT_ME(read, stream_socket_read_arginfo) + P3_ABSTRACT_ME(write, stream_socket_write_arginfo) + PHP_FE_END +}; + +static zend_function_entry datagram_socket_methods[] = { + P3_ABSTRACT_ME(recvFrom, datagram_socket_recv_from_arginfo) + PHP_FE_END +}; + +static zend_function_entry tcp_socket_methods[] = { + P3_ME(asio::tcp_socket, open, open_inet, inet_socket_open_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::tcp_socket, assign, assign_inet, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::tcp_socket, bind, bind_inet, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, connect, tcp_socket_connect_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, remoteAddr, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, remotePort, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_socket, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry udp_socket_methods[] = { + P3_ME(asio::udp_socket, open, open_inet, inet_socket_open_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::udp_socket, assign, assign_inet, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::udp_socket, bind, bind_inet, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, sendTo, udp_socket_send_to_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, recvFrom, datagram_socket_recv_from_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, remoteAddr, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, remotePort, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udp_socket, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry unix_socket_methods[] = { + P3_ME(asio::unix_socket, open, open_local, nullptr, ZEND_ACC_PUBLIC) + P3_ME(asio::unix_socket, assign, assign_local, local_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::unix_socket, bind, bind_local, local_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, connect, unix_socket_connect_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, remotePath, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_socket, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry udg_socket_methods[] = { + P3_ME(asio::udg_socket, open, open_local, nullptr, ZEND_ACC_PUBLIC) + P3_ME(asio::udg_socket, assign, assign_local, local_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME(asio::udg_socket, bind, bind_local, local_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, sendTo, udg_socket_send_to_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, recvFrom, datagram_socket_recv_from_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, remotePath, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, available, socket_available_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, atMark, socket_at_mark_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::udg_socket, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry acceptor_methods[] = { + P3_ABSTRACT_ME(listen, acceptor_listen_arginfo) + P3_ABSTRACT_ME(accept, acceptor_accept_arginfo) + P3_ABSTRACT_ME(close, nullptr) + PHP_FE_END +}; + +static zend_function_entry tcp_acceptor_methods[] = { + P3_ME_D(asio::tcp_acceptor, open, inet_socket_open_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, assign, inet_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, bind, inet_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, listen, acceptor_listen_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, accept, acceptor_accept_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::tcp_acceptor, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry unix_acceptor_methods[] = { + P3_ME_D(asio::unix_acceptor, open, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, assign, local_socket_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, bind, local_socket_bind_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, listen, acceptor_listen_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, accept, acceptor_accept_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, cancel, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::unix_acceptor, close, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static zend_function_entry stream_descriptor_methods[] = { + P3_ME_D(asio::stream_descriptor, assign, stream_descriptor_assign_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::stream_descriptor, isOpen, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::stream_descriptor, read, stream_socket_read_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::stream_descriptor, write, stream_socket_write_arginfo, ZEND_ACC_PUBLIC) + P3_ME_D(asio::stream_descriptor, release, nullptr, ZEND_ACC_PUBLIC) + P3_ME_D(asio::stream_descriptor, cancel, nullptr, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +/* }}} */ + +/* {{{ interfaces class_entry */ + +auto io_object_ce = p3::interface_init("Asio\\IoObject", io_object_method); +auto resolver_ce = p3::interface_init("Asio\\Resolver", resolver_methods); +auto socket_ce = p3::interface_init("Asio\\Socket", socket_methods); +auto inet_socket_ce = p3::interface_init("Asio\\InetSocket", inet_socket_methods); +auto local_socket_ce = p3::interface_init("Asio\\LocalSocket", local_socket_methods); +auto stream_socket_ce = p3::interface_init("Asio\\StreamSocket", stream_socket_methods); +auto datagram_socket_ce = p3::interface_init("Asio\\DatagramSocket", datagram_socket_methods); +auto acceptor_ce = p3::interface_init("Asio\\Acceptor", acceptor_methods); + +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(asio) +{ + socket_ce->parent = io_object_ce; + resolver_ce->parent = io_object_ce; + acceptor_ce->parent = io_object_ce; + inet_socket_ce->parent = socket_ce; + local_socket_ce->parent = socket_ce; + stream_socket_ce->parent = socket_ce; + datagram_socket_ce->parent = socket_ce; + p3::class_init("Asio\\Service", service_methods); + auto ce = p3::class_init("Asio\\Timer", timer_methods); + zend_class_implements(ce, 1, io_object_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\Signal", signal_methods); + zend_class_implements(ce, 1, io_object_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\TcpResolver", tcp_resolver_methods); + zend_class_implements(ce, 1, resolver_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\UdpResolver", udp_resolver_methods); + zend_class_implements(ce, 1, resolver_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\TcpSocket", tcp_socket_methods); + zend_class_implements(ce, 2, inet_socket_ce, stream_socket_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\UdpSocket", udp_socket_methods); + zend_class_implements(ce, 2, inet_socket_ce, datagram_socket_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\UnixSocket", unix_socket_methods); + zend_class_implements(ce, 2, local_socket_ce, stream_socket_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\UdgSocket", udg_socket_methods); + zend_class_implements(ce, 2, local_socket_ce, datagram_socket_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\TcpAcceptor", tcp_acceptor_methods); + zend_class_implements(ce, 1, acceptor_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\UnixAcceptor", unix_acceptor_methods); + zend_class_implements(ce, 1, acceptor_ce); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\StreamDescriptor", stream_descriptor_methods); + zend_class_implements(ce, 1, io_object_ce); + ce->ce_flags |= ZEND_ACC_FINAL; +#ifdef ENABLE_COROUTINE + ce = p3::class_init("Asio\\Future", nullptr); + ce->ce_flags |= ZEND_ACC_FINAL; +#endif // ENABLE_COROUTINE +#ifdef ENABLE_STRAND + ce = p3::class_init("Asio\\Strand", strand_methods); + ce->ce_flags |= ZEND_ACC_FINAL; + ce = p3::class_init("Asio\\WrappedHandler", wrapped_handler_methods); + ce->ce_flags |= ZEND_ACC_FINAL; +#endif // ENABLE_STRAND + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(asio) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Coroutine support", +#ifdef ENABLE_COROUTINE + "enabled" +#else + "disabled" +#endif // ENABLE_COROUTINE + ); + php_info_print_table_header(2, "Strand support", +#ifdef ENABLE_STRAND + "enabled" +#else + "disabled" +#endif // ENABLE_STRAND + ); + php_info_print_table_header(2, "Null buffers support", +#ifdef ENABLE_NULL_BUFFERS + "enabled" +#else + "disabled" +#endif // ENABLE_NULL_BUFFERS + ); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ asio_module_entry */ +zend_module_entry asio_module_entry = { + STANDARD_MODULE_HEADER, + "asio", + nullptr, + PHP_MINIT(asio), + nullptr, + nullptr, + nullptr, + PHP_MINFO(asio), + PHP_ASIO_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_ASIO +ZEND_GET_MODULE(asio) +#endif // COMPILE_DL_ASIO diff --git a/src/php_asio.hpp b/src/php_asio.hpp new file mode 100644 index 0000000..7583416 --- /dev/null +++ b/src/php_asio.hpp @@ -0,0 +1,26 @@ +/** + * php-asio/php_asio.hpp + * + * @author CismonX + */ + +#pragma once + +extern zend_module_entry asio_module_entry; +#define phpext_asio_ptr &asio_module_entry + +#define PHP_ASIO_VERSION "0.1.5" + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define PHP_ASIO_API __attribute__ ((visibility("default"))) +#else +#define PHP_ASIO_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if defined(ZTS) && defined(COMPILE_DL_ASIO) +ZEND_TSRMLS_CACHE_EXTERN() +#endif diff --git a/src/resolver.cpp b/src/resolver.cpp new file mode 100644 index 0000000..e7ec6c7 --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,70 @@ +/** + * php-asio/resolver.cpp + * + * @author CismonX + */ + +#include "resolver.hpp" +#include "future.hpp" + +namespace asio +{ + template + zval* resolver::handler(const boost::system::error_code& error, + iterator iter, zval* callback, zval* argument) + { + iterator end; + ZVAL_INIT(addr_list); + ZVAL_NEW_ARR(&addr_list); + zend_hash_init(Z_ARR(addr_list), 0, nullptr, ZVAL_PTR_DTOR, 0); + ZVAL_INIT(addr_val); + while (iter != end) { + auto addr = (*iter++).endpoint().address().to_string(); + ZVAL_STR(&addr_val, zend_string_init(addr.c_str(), addr.length(), 0)); + zend_hash_next_index_insert(Z_ARR(addr_list), &addr_val); + } + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_ARR(&arguments[1], Z_ARR(addr_list)); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_ARR, Z_ARR(addr_list)); + } + + template + P3_METHOD(resolver, resolve) + { + zend_string* host; + zend_string* service = nullptr; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STR(host) + Z_PARAM_OPTIONAL + Z_PARAM_STR(service) + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_FUTURE_INIT(); + future->template on_resolve(boost::bind( + &resolver::handler, this, _1, _2, cb, args)); + resolver_.async_resolve({ ZSTR_VAL(host), service ? ZSTR_VAL(service) : "" }, + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(iterator))); + FUTURE_RETURN(); + } + + template + P3_METHOD(resolver, cancel) + { + resolver_.cancel(); + RETVAL_LONG(0); + } + + template + zend_class_entry* resolver::class_entry; + + template + zend_object_handlers resolver::handlers; + + template class resolver; + template class resolver; +} diff --git a/src/resolver.hpp b/src/resolver.hpp new file mode 100644 index 0000000..185294c --- /dev/null +++ b/src/resolver.hpp @@ -0,0 +1,51 @@ +/** + * php-asio/resolver.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio resolver. + /// Provides hostname resolution. + template + class resolver : public base + { + /// Resolver iterator which holds an endpoint. + using iterator = typename Protocol::resolver::iterator; + + /// Boost.Asio resolver instance. + typename Protocol::resolver resolver_; + + /// Resolve handler. + zval* handler(const boost::system::error_code& error, + iterator iter, zval* callback, zval* argument); + + public: + /// Constructor. + explicit resolver( + boost::asio::io_service& io_service + ) : base(io_service), resolver_(io_service) {} + + /* {{{ proto Future Resolver::resolve(string host, [string service = ""], + * [callable callback], [mixed argument]); + * Initiate an asynchronous resolve against the resolver. */ + P3_METHOD_DECLARE(resolve); + /* }}} */ + + /* {{{ proto int Resolver::cancel(void); + * Cancel all asynchronous operations on this resolver. */ + P3_METHOD_DECLARE(cancel); + /* }}} */ + + PHP_ASIO_CE_DECLARE(); + }; + + using tcp_resolver = resolver; + using udp_resolver = resolver; +} diff --git a/src/service.cpp b/src/service.cpp new file mode 100644 index 0000000..5327637 --- /dev/null +++ b/src/service.cpp @@ -0,0 +1,170 @@ +/** + * php-asio/service.cpp + * + * @author CismonX + */ + +#include "service.hpp" + +#define PHP_ASIO_RUN_LOOP(meth) \ + zval* error = nullptr; \ + ZEND_PARSE_PARAMETERS_START(0, 1) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_ZVAL(error) \ + ZEND_PARSE_PARAMETERS_END(); \ + boost::system::error_code ec; \ + auto handler_count = meth(ec); \ + if (error) { \ + ZVAL_DEREF(error); \ + ZVAL_LONG(error, static_cast(ec.value())); \ + } \ + RETVAL_LONG(static_cast(handler_count)) + +#define PHP_ASIO_NOTIFY_FORK(ev) \ + try { \ + io_service_.notify_fork(boost::asio::io_service::ev); \ + } catch (const boost::system::system_error& err) { \ + RETURN_LONG(static_cast(err.code().value())); \ + } \ + RETVAL_LONG(0) + +#define SERVICE_DISPATCH_CALLBACK(meth) PHP_ASIO_DISPATCH_CALLBACK(meth) }) + +namespace asio +{ + P3_METHOD(service, addTimer) + { + PHP_ASIO_OBJ_ALLOC(timer, asio::timer, io_service_); + RETVAL_OBJ(timer); + } + + P3_METHOD(service, addSignal) + { + PHP_ASIO_OBJ_ALLOC(signal, asio::signal, io_service_); + RETVAL_OBJ(signal); + } + + P3_METHOD(service, addTcpResolver) + { + PHP_ASIO_OBJ_ALLOC(resolver, tcp_resolver, io_service_); + RETVAL_OBJ(resolver); + } + + P3_METHOD(service, addUdpResolver) + { + PHP_ASIO_OBJ_ALLOC(resolver, udp_resolver, io_service_); + RETVAL_OBJ(resolver); + } + + P3_METHOD(service, addTcpSocket) + { + PHP_ASIO_OBJ_ALLOC(socket, tcp_socket, io_service_); + RETVAL_OBJ(socket); + } + + P3_METHOD(service, addUdpSocket) + { + PHP_ASIO_OBJ_ALLOC(socket, udp_socket, io_service_); + RETVAL_OBJ(socket); + } + + P3_METHOD(service, addUnixSocket) + { + PHP_ASIO_OBJ_ALLOC(socket, unix_socket, io_service_); + RETVAL_OBJ(socket); + } + + P3_METHOD(service, addUdgSocket) + { + PHP_ASIO_OBJ_ALLOC(socket, udg_socket, io_service_); + RETVAL_OBJ(socket); + } + + P3_METHOD(service, addTcpAcceptor) + { + PHP_ASIO_OBJ_ALLOC(acceptor, tcp_acceptor, io_service_); + RETVAL_OBJ(acceptor); + } + + P3_METHOD(service, addUnixAcceptor) + { + PHP_ASIO_OBJ_ALLOC(acceptor, unix_acceptor, io_service_); + RETVAL_OBJ(acceptor); + } + + P3_METHOD(service, addStreamDescriptor) + { + PHP_ASIO_OBJ_ALLOC(descriptor, stream_descriptor, io_service_); + RETVAL_OBJ(descriptor); + } + +#ifdef ENABLE_STRAND + P3_METHOD(service, addStrand) + { + PHP_ASIO_OBJ_ALLOC(strand, asio::strand, io_service_); + RETVAL_OBJ(strand); + } +#endif // ENABLE_STRAND + + P3_METHOD(service, run) + { + PHP_ASIO_RUN_LOOP(io_service_.run); + } + + P3_METHOD(service, runOne) + { + PHP_ASIO_RUN_LOOP(io_service_.run_one); + } + + P3_METHOD(service, poll) + { + PHP_ASIO_RUN_LOOP(io_service_.poll); + } + + P3_METHOD(service, pollOne) + { + PHP_ASIO_RUN_LOOP(io_service_.poll_one); + } + + P3_METHOD(service, stop) + { + io_service_.stop(); + } + + P3_METHOD(service, reset) + { + io_service_.reset(); + } + + P3_METHOD(service, stopped) const + { + RETVAL_BOOL(io_service_.stopped()); + } + + P3_METHOD(service, post) + { + SERVICE_DISPATCH_CALLBACK(io_service_.post); + } + + P3_METHOD(service, dispatch) + { + SERVICE_DISPATCH_CALLBACK(io_service_.dispatch); + } + + P3_METHOD(service, forkPrepare) + { + PHP_ASIO_NOTIFY_FORK(fork_prepare); + } + + P3_METHOD(service, forkParent) + { + PHP_ASIO_NOTIFY_FORK(fork_parent); + } + + P3_METHOD(service, forkChild) + { + PHP_ASIO_NOTIFY_FORK(fork_child); + } + + PHP_ASIO_CE_DEFINE(service); +} \ No newline at end of file diff --git a/src/service.hpp b/src/service.hpp new file mode 100644 index 0000000..4f2cdcf --- /dev/null +++ b/src/service.hpp @@ -0,0 +1,171 @@ +/** + * php-asio/service.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "future.hpp" +#include "timer.hpp" +#include "signal.hpp" +#include "resolver.hpp" +#include "socket.hpp" +#include "acceptor.hpp" +#include "stream_descriptor.hpp" +#include "strand.hpp" +#include "php_asio.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio io_service. + /// Provide access to instantiation of I/O objects. + class service + { + /// The io_service of all I/O objects in current instance. + boost::asio::io_service io_service_; + + public: + /// Default constructor. + service() = default; + + /// Deleted copy constructor. + service(const service&) = delete; + + /// Default destructor. + virtual ~service() = default; + + /// Deleted copy assignment operator. + service& operator=(const service&) = delete; + + /* {{{ proto Timer Service::addTimer(void); + * Add a new timer. */ + P3_METHOD_DECLARE(addTimer); + /* }}} */ + + /* {{{ proto Signal Service::addSignal(void); + * Add a new signal set. */ + P3_METHOD_DECLARE(addSignal); + /* }}} */ + + /* {{{ proto TcpResolver Service::addTcpResolver(void); + * Add a new TCP resolver. */ + P3_METHOD_DECLARE(addTcpResolver); + /* }}} */ + + /* {{{ proto UdpResolver Service::addUdpResolver(void); + * Add a new UDP resolver.*/ + P3_METHOD_DECLARE(addUdpResolver); + /* }}} */ + + /* {{{ proto TcpSocket Service::addUdpSocket(void); + * Add a new TCP socket. */ + P3_METHOD_DECLARE(addTcpSocket); + /* }}} */ + + /* {{{ proto UdpSocket Service::addUdpSocket(void); + * Add a new UDP socket. */ + P3_METHOD_DECLARE(addUdpSocket); + /* }}} */ + + /* {{{ proto UnixSocket Service::addUnixSocket(void); + * Add a new UNIX domain socket (SOCK_STREAM). */ + P3_METHOD_DECLARE(addUnixSocket); + /* }}} */ + + /* {{{ proto UdgSocket Service::addUdgSocket(void); + * Add a new UNIX domain socket (SOCK_DGRAM). */ + P3_METHOD_DECLARE(addUdgSocket); + /* }}} */ + + /* {{{ proto TcpAcceptor Service::addTcpAcceptor(void); + * Add a new TCP acceptor. */ + P3_METHOD_DECLARE(addTcpAcceptor); + /* }}} */ + + /* {{{ proto UnixAcceptor Service::addUnixAcceptor(void); + * Add a new UNIX domain socket acceptor (SOCK_STREAM). */ + P3_METHOD_DECLARE(addUnixAcceptor); + /* }}} */ + + /* {{{ proto StreamDescriptor Service::addStreamDescriptor(void); + * Add a new stream descriptor. */ + P3_METHOD_DECLARE(addStreamDescriptor); + /* }}} */ + +#ifdef ENABLE_STRAND + /* {{{ proto Strand Service::addStrand(void); + * Add a new strand object. */ + P3_METHOD_DECLARE(addStrand); + /* }}} */ +#endif // ENABLE_STRAND + + /* {{{ proto int run([int &ec]); + * Run the event loop until stopped or no more work. */ + P3_METHOD_DECLARE(run); + /* }}} */ + + /* {{{ proto int runOne([int &ec]); + * Run until stopped or one operation is performed. */ + P3_METHOD_DECLARE(runOne); + /* }}} */ + + /* {{{ proto int poll([int &ec]); + * Poll for operations without blocking. */ + P3_METHOD_DECLARE(poll); + /* }}} */ + + /* {{{ proto int pollOne([int &ec]); + * Poll for one operation without blocking. */ + P3_METHOD_DECLARE(pollOne); + /* }}} */ + + /* {{{ proto void stop(void); + * Stop the event processing loop. */ + P3_METHOD_DECLARE(stop); + /* }}} */ + + /* {{{ proto void stop(void); + * Reset in preparation for a subsequent run invocation. */ + P3_METHOD_DECLARE(reset); + /* }}} */ + + /* {{{ proto bool stopped(void); + * Determine whether the io_service is stopped. */ + P3_METHOD_DECLARE(stopped) const; + /* }}} */ + + /* {{{ proto void Service::post(callable callback, [mixed argument]); + * Request invocation of the given handler and return immediately. */ + P3_METHOD_DECLARE(post); + /* }}} */ + + /* {{{ proto void Service::dispatch(callable callback, [mixed argument]); + * Request invocation of the given handler. */ + P3_METHOD_DECLARE(dispatch); + + /* {{{ proto int Service::forkPrepare(void); + * Notify all services of a fork event. (prepare to fork) */ + P3_METHOD_DECLARE(forkPrepare); + /* }}} */ + + /* {{{ proto int Service::forkParent(void); + * Notify all services of a fork event. (fork parent) */ + P3_METHOD_DECLARE(forkParent); + /* }}} */ + + /* {{{ proto int Service::forkChild(void); + * Notify all services of a fork event. (fork child) */ + P3_METHOD_DECLARE(forkChild); + + /// Get io_service by reference. + /// Can be used when working on another extension based on Boost.Asio. + PHP_ASIO_API boost::asio::io_service& get_io_service() + { + return io_service_; + } + + PHP_ASIO_CE_DECLARE(); + }; +} diff --git a/src/signal.cpp b/src/signal.cpp new file mode 100644 index 0000000..f3b5907 --- /dev/null +++ b/src/signal.cpp @@ -0,0 +1,90 @@ +/** + * php-asio/signal.cpp + * + * @author CismonX + */ + +#include "signal.hpp" +#include "future.hpp" + +namespace asio +{ + zval* signal::handler(const boost::system::error_code& error, + int signal, zval* callback, zval* argument) + { + PHP_ASIO_INVOKE_CALLBACK_START(4); + ZVAL_LONG(&arguments[1], static_cast(signal)); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_LONG, static_cast(signal)); + } + + P3_METHOD(signal, add) + { + size_t sig_len = 0; + zval* sig_num = nullptr; + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_VARIADIC('+', sig_num, sig_len) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + for (size_t i = 0; i < sig_len; i++) { + if (Z_TYPE(sig_num[i]) != IS_LONG) { + PHP_ASIO_ERROR(E_NOTICE, "Integer value expected."); + continue; + } + signal_.add(static_cast(Z_LVAL(sig_num[i])), ec); + if (ec) + break; + } + RETVAL_EC(ec); + } + + P3_METHOD(signal, remove) + { + size_t sig_len = 0; + zval* sig_num = nullptr; + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_VARIADIC('+', sig_num, sig_len) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + for (size_t i = 0; i < sig_len; i++) { + if (Z_TYPE(sig_num[i]) != IS_LONG) { + PHP_ASIO_ERROR(E_NOTICE, "Integer value expected."); + continue; + } + signal_.remove(static_cast(Z_LVAL(sig_num[i])), ec); + if (ec) + break; + } + RETVAL_EC(ec); + } + + P3_METHOD(signal, wait) + { + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind(&signal::handler, this, _1, _2, cb, args)); + signal_.async_wait(STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(int))); + FUTURE_RETURN(); + } + + P3_METHOD(signal, clear) + { + boost::system::error_code ec; + RETVAL_EC(signal_.clear(ec)); + } + + P3_METHOD(signal, cancel) + { + boost::system::error_code ec; + RETVAL_EC(signal_.cancel(ec)); + } + + PHP_ASIO_CE_DEFINE(signal); +} diff --git a/src/signal.hpp b/src/signal.hpp new file mode 100644 index 0000000..c34832d --- /dev/null +++ b/src/signal.hpp @@ -0,0 +1,58 @@ +/** + * php-asio/signal.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio signal_set. + /// Provides functionalities for signal handling. + class signal : public base + { + /// Boost.Asio signal_set instance. + boost::asio::signal_set signal_; + + /// Signal handler callback. + zval* handler(const boost::system::error_code& error, + int signal, zval* callback, zval* argument); + + public: + /// Constructor. + explicit signal( + boost::asio::io_service& io_service + ) : base(io_service), signal_(io_service) {} + + /* {{{ proto int Signal::add(int sig_num, [int ...]); + * Add the specified signal(s) to the signal set. */ + P3_METHOD_DECLARE(add); + /* }}} */ + + /* {{{ proto int Signal::remove(int sig_num, [int ...]); + * Remove the specified signal(s) from the signal set. */ + P3_METHOD_DECLARE(remove); + /* }}} */ + + /* {{{ proto Future Signal::wait([callable callback], [mixed argument]); + * Initiate an asynchronous wait against the signal set. */ + P3_METHOD_DECLARE(wait); + /* }}} */ + + /* {{{ proto int Signal::clear(void); + * Remove all signals from the signal set. */ + P3_METHOD_DECLARE(clear); + /* }}} */ + + /* {{{ proto int Signal::cancel(void); + * Cancel current signal set. */ + P3_METHOD_DECLARE(cancel); + /* }}} */ + + PHP_ASIO_CE_DECLARE(); + }; +} diff --git a/src/socket.cpp b/src/socket.cpp new file mode 100644 index 0000000..2aa00a6 --- /dev/null +++ b/src/socket.cpp @@ -0,0 +1,480 @@ +/** + * php-asio/socket.cpp + * + * @author CismonX + */ + +#include "socket.hpp" +#include "future.hpp" +#include "io.hpp" + +namespace asio +{ + template + zval* socket::connect_handler(const boost::system::error_code& error, + zval* callback, zval* argument) + { + PHP_ASIO_INVOKE_CALLBACK_START(3) + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN_NULL(); + } + + template template + zval* socket::read_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument) + { + // Zend strings have to be zero-terminated. + // Otherwise you'll get E_WARNING on zval dtor. + ZSTR_VAL(buffer)[length] = '\0'; + ZSTR_LEN(buffer) = length; + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_STR(&arguments[1], buffer); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_STR, buffer); + } + + template + zval* socket::write_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument) + { +#ifdef ENABLE_NULL_BUFFERS + if (buffer) +#endif //ENABLE_NULL_BUFFERS + zend_string_release(buffer); + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_LONG(&arguments[1], static_cast(length)); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_LONG, static_cast(length)); + } + + template <> + zval* udp_socket::recv_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, udp::endpoint* endpoint, + zval* callback, zval* argument) + { + ZSTR_VAL(buffer)[length] = '\0'; + ZSTR_LEN(buffer) = length; +#ifdef ENABLE_COROUTINE + last_addr_<> = endpoint->address().to_string(); + last_port_<> = endpoint->port(); +#endif // ENABLE_COROUTINE + PHP_ASIO_INVOKE_CALLBACK_START(6) + ZVAL_STR(&arguments[1], buffer); +#ifdef ENABLE_COROUTINE + ZVAL_STRING(&arguments[2], last_addr_<>.data()); + ZVAL_LONG(&arguments[3], static_cast(last_port_<>)); +#else + ZVAL_STRING(&arguments[2], endpoint->address().to_string().data()); + ZVAL_LONG(&arguments[3], static_cast(endpoint->port())); +#endif // ENABLE_COROUTINE + PHP_ASIO_INVOKE_CALLBACK(); + zval_ptr_dtor(&arguments[2]); + PHP_ASIO_INVOKE_CALLBACK_END(); + delete endpoint; + CORO_RETURN(ZVAL_STR, buffer); + } + + template <> + zval* udg_socket::recv_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, udg::endpoint* endpoint, + zval* callback, zval* argument) + { + ZSTR_VAL(buffer)[length] = '\0'; + ZSTR_LEN(buffer) = length; +#ifdef ENABLE_COROUTINE + last_path_<> = endpoint->path(); +#endif // ENABLE_COROUTINE + PHP_ASIO_INVOKE_CALLBACK_START(5) + ZVAL_STR(&arguments[1], buffer); +#ifdef ENABLE_COROUTINE + ZVAL_STRING(&arguments[2], last_path_<>.data()); +#else + ZVAL_STRING(&arguments[2], endpoint->path().data()); +#endif // ENABLE_COROUTINE + PHP_ASIO_INVOKE_CALLBACK(); + zval_ptr_dtor(&arguments[2]); + PHP_ASIO_INVOKE_CALLBACK_END(); + delete endpoint; + CORO_RETURN(ZVAL_STR, buffer); + } + + template template + P3_METHOD(socket, open_inet) + { + zend_bool inet6; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_BOOL(inet6) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + socket_.open(inet6 ? Protocol::v6() : Protocol::v4(), ec); + RETVAL_EC(ec); + } + + template template + P3_METHOD(socket, open_local) + { + boost::system::error_code ec; + socket_.open(Protocol(), ec); + RETVAL_EC(ec); + } + + template template + P3_METHOD(socket, assign_inet) + { + PHP_ASIO_INET_ASSIGN(socket_, Protocol); + } + + template template + P3_METHOD(socket, assign_local) + { + PHP_ASIO_LOCAL_ASSIGN(socket_, Protocol); + } + + template template + P3_METHOD(socket, bind_inet) + { + zend_string* address; + zend_long port_num; + ZEND_PARSE_PARAMETERS_START(2, 2); + Z_PARAM_STR(address) + Z_PARAM_LONG(port_num) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + socket_.bind({ boost::asio::ip::address::from_string(ZSTR_VAL(address)), + static_cast(port_num) }, ec); + RETVAL_EC(ec); + } + + template template + P3_METHOD(socket, bind_local) + { + zend_string* socket_path; + ZEND_PARSE_PARAMETERS_START(1, 1); + Z_PARAM_STR(socket_path) + ZEND_PARSE_PARAMETERS_END(); + namespace fs = boost::filesystem; + if (fs::status(ZSTR_VAL(socket_path)).type() == fs::socket_file) + fs::remove(ZSTR_VAL(socket_path)); + boost::system::error_code ec; + socket_.bind({ ZSTR_VAL(socket_path) }, ec); + RETVAL_EC(ec); + } + + /* {{{ proto Future TcpSocket::connect(string address, int port, + * [callable callback], [mixed argument]); + * Asynchronously connect to a remote endpoint. */ + template <> + P3_METHOD(tcp_socket, connect) + { + zend_string* address; + zend_long port; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(address) + Z_PARAM_LONG(port) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind(&socket::connect_handler, this, _1, cb, args)); + socket_.async_connect({ boost::asio::ip::address::from_string(ZSTR_VAL(address)), + static_cast(port) }, STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG)); + FUTURE_RETURN(); + } + /* }}} */ + + /* {{{ proto Future UnixSocket::connect(string path, [callable callback], [mixed argument]); + * Asynchronously connect to a remote endpoint. */ + template <> + P3_METHOD(unix_socket, connect) + { + zend_string* path; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(path) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind(&socket::connect_handler, this, _1, cb, args)); + socket_.async_connect({ ZSTR_VAL(path) }, STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG)); + FUTURE_RETURN(); + } + /* }}} */ + + template template + P3_METHOD(socket, read) + { + PHP_ASIO_READ(socket, socket_); + } + + template template + P3_METHOD(socket, write) + { + PHP_ASIO_WRITE(socket, socket_); + } + + template template + P3_METHOD(socket, recvFrom) + { + zend_long length; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(1, 3); + Z_PARAM_LONG(length) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_BUFFER_LEN_VALIDATE(); + auto buffer_container = +#ifdef ENABLE_NULL_BUFFERS + length == 0 ? ZSTR_EMPTY_ALLOC() : +#endif //ENABLE_NULL_BUFFERS + zend_string_alloc(static_cast(length), 0); + auto endpoint = new typename Protocol::endpoint; + PHP_ASIO_FUTURE_INIT(); + future->template on_resolve(boost::bind(&socket::recv_handler, + this, _1, _2, buffer_container, endpoint, cb, args)); +#ifdef ENABLE_NULL_BUFFERS + if (length == 0) + socket_.async_receive_from(boost::asio::null_buffers(), + *endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + else +#endif // ENABLE_NULL_BUFFERS + socket_.async_receive_from(mutable_buffer(buffer_container), + *endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + FUTURE_RETURN(); + } + + /* {{{ proto Future UdpSocket::sendTo(string data, string address, int port, + * [callable callback], [mixed argument]); + * Send asynchronously to UDP socket. */ + template <> template <> + P3_METHOD(udp_socket, sendTo) + { + zend_string* data; + zend_string* address; + zend_long port; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(3, 5) + Z_PARAM_STR(data) + Z_PARAM_STR(address) + Z_PARAM_LONG(port) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + const udp::endpoint endpoint(boost::asio::ip::address::from_string(ZSTR_VAL(address)), + static_cast(port)); + const auto buffer_container = +#ifdef ENABLE_NULL_BUFFERS + ZSTR_LEN(data) == 0 ? nullptr : +#endif //ENABLE_NULL_BUFFERS + zend_string_copy(data); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind(&socket::write_handler, + this, _1, _2, buffer_container, cb, args)); +#ifdef ENABLE_NULL_BUFFERS + if (ZSTR_LEN(data) == 0) + socket_.async_send_to(boost::asio::null_buffers(), + endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + else +#endif // ENABLE_NULL_BUFFERS + socket_.async_send_to(const_buffer(buffer_container), + endpoint, STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + FUTURE_RETURN(); + } + /* }}} */ + + /* {{{ proto Future UdgSocket::sendTo(string data, string path, + * [callable callback], [mixed argument]); + * Send asynchronously to UNIX datagram socket. */ + template <> template <> + P3_METHOD(udg_socket, sendTo) + { + zend_string* data; + zend_string* path; + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(data) + Z_PARAM_STR(path) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + const auto buffer_container = +#ifdef ENABLE_NULL_BUFFERS + ZSTR_LEN(data) == 0 ? nullptr : +#endif //ENABLE_NULL_BUFFERS + zend_string_copy(data); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind( + &socket::write_handler, this, _1, _2, buffer_container, cb, args)); +#ifdef ENABLE_NULL_BUFFERS + if (ZSTR_LEN(data) == 0) + socket_.async_send_to(boost::asio::null_buffers(), { ZSTR_VAL(path) }, + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + else +#endif //ENABLE_NULL_BUFFERS + socket_.async_send_to(const_buffer(buffer_container), { ZSTR_VAL(path) }, + STRAND_RESOLVE(ASYNC_HANDLER_DOUBLE_ARG(size_t))); + FUTURE_RETURN(); + } + /* }}} */ + + template <> template <> + P3_METHOD(tcp_socket, remoteAddr) const + { + RETVAL_STRING(socket_.remote_endpoint().address().to_string().c_str()); + } + + template <> template <> + P3_METHOD(tcp_socket, remotePort) const + { + RETVAL_LONG(static_cast(socket_.remote_endpoint().port())); + } + + template <> template <> + P3_METHOD(unix_socket, remotePath) const + { + RETVAL_STRING(socket_.remote_endpoint().path().c_str()); + } + + template <> template <> + P3_METHOD(udp_socket, remoteAddr) const + { +#ifdef ENABLE_COROUTINE + RETVAL_STRING(last_addr_<>.data()); +#else + PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support."); + RETVAL_NULL(); +#endif // ENABLE_COROUTINE + } + + template <> template <> + P3_METHOD(udp_socket, remotePort) const + { +#ifdef ENABLE_COROUTINE + RETVAL_LONG(static_cast(last_port_<>)); +#else + PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support."); + RETVAL_NULL(); +#endif // ENABLE_COROUTINE + } + + template <> template <> + P3_METHOD(udg_socket, remotePath) const + { +#ifdef ENABLE_COROUTINE + RETVAL_STRING(last_path_<>.data()); +#else + PHP_ASIO_ERROR(E_WARNING, "Not available without coroutine support."); + RETVAL_NULL(); +#endif // ENABLE_COROUTINE + } + + template + P3_METHOD(socket, available) const + { + zval* error = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(error) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + auto bytes = socket_.available(ec); + if (error) { + ZVAL_DEREF(error); + ZVAL_LONG(error, static_cast(ec.value())); + } + RETVAL_LONG(static_cast(bytes)); + } + + template + P3_METHOD(socket, atMark) const + { + zval* error = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(error) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + auto at_mark = socket_.at_mark(ec); + if (error) { + ZVAL_DEREF(error); + ZVAL_LONG(error, static_cast(ec.value())); + } + RETVAL_BOOL(at_mark); + } + + template + P3_METHOD(socket, cancel) + { + boost::system::error_code ec; + RETVAL_EC(socket_.cancel(ec)); + } + + template + P3_METHOD(socket, close) + { + boost::system::error_code ec; + RETVAL_EC(socket_.close(ec)); + } + + template + zend_class_entry* socket::class_entry; + + template + zend_object_handlers socket::handlers; + +#ifdef ENABLE_COROUTINE + template template + thread_local std::string socket::last_addr_; + + template template + thread_local unsigned short socket::last_port_; + + template template + thread_local std::string socket::last_path_; +#endif // ENABLE_COROUTINE + + template class socket; + template P3_METHOD(tcp_socket, open_inet); + template P3_METHOD(tcp_socket, assign_inet); + template P3_METHOD(tcp_socket, bind_inet); + template zval* tcp_socket::read_handler(const boost::system::error_code&, + size_t, zend_string*, zval*, zval*); + template P3_METHOD(tcp_socket, read); + template P3_METHOD(tcp_socket, write); + + template class socket; + template P3_METHOD(unix_socket, open_local); + template P3_METHOD(unix_socket, assign_local); + template P3_METHOD(unix_socket, bind_local); + template zval* unix_socket::read_handler(const boost::system::error_code&, + size_t, zend_string*, zval*, zval*); + template P3_METHOD(unix_socket, read); + template P3_METHOD(unix_socket, write); + + template class socket; + template P3_METHOD(udp_socket, open_inet); + template P3_METHOD(udp_socket, assign_inet); + template P3_METHOD(udp_socket, bind_inet); + template P3_METHOD(udp_socket, recvFrom); + + template class socket; + template P3_METHOD(udg_socket, open_local); + template P3_METHOD(udg_socket, assign_local); + template P3_METHOD(udg_socket, bind_local); + template P3_METHOD(udg_socket, recvFrom); +} diff --git a/src/socket.hpp b/src/socket.hpp new file mode 100644 index 0000000..34d13ad --- /dev/null +++ b/src/socket.hpp @@ -0,0 +1,188 @@ +/** + * php-asio/socket.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +#include + +#define ENABLE_IF_INET(P) std::string(P::endpoint::*port)() +#define ENABLE_IF_LOCAL(P) std::string(P::endpoint::*path)() + +namespace asio +{ + /// Wrapper for Boost.Asio socket. + template + class socket : public base + { + /// Boost.Asio socket instance. + typename Protocol::socket socket_; + + template + using enable_if_same = std::enable_if_t::value>; + +#ifdef ENABLE_COROUTINE + /// Address of client who sent the last request. + template > + static thread_local std::string last_addr_; + + /// Port of client who sent the last request. + template > + static thread_local unsigned short last_port_; + + /// Socket path of client who sent the last request. + template > + static thread_local std::string last_path_; +#endif // ENABLE_COROUTINE + + /// Connect handler. + zval* connect_handler(const boost::system::error_code& error, + zval* callback, zval* argument); + + /// Read handler for stream socket. + template , typename P::socket>> + zval* read_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument); + + /// Write/send handler for socket. + zval* write_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument); + + /// Receive handler for datagram socket. + zval* recv_handler(const boost::system::error_code& error, size_t length, + zend_string* buffer, typename Protocol::endpoint* endpoint, + zval* callback, zval* argument); + + public: + /// Constructor. + explicit socket( + boost::asio::io_service& io_service + ) : base(io_service), socket_(io_service) {} + + /// Get reference of socket. + typename Protocol::socket& get_socket() + { + return socket_; + } + + template + using inet_p = typename P::endpoint::port; + + /* {{{ proto int InetSocket::open(bool inet6); + * Open INET socket (AF_INET or AF_INET6). */ + template + P3_METHOD_DECLARE(open_inet); + /* }}} */ + + /* {{{ proto int LocalSocket::open(void); + * Open UNIX domain socket. */ + template + P3_METHOD_DECLARE(open_local); + /* }}} */ + + /* {{{ proto int InetSocket::assign(bool inet6, int|resource fd); + * Assign an existing native socket to the socket. */ + template + P3_METHOD_DECLARE(assign_inet); + /* }}} */ + + /* {{{ proto int LocalSocket::assign(int|resource fd); + * Assign an existing native socket to the socket. */ + template + P3_METHOD_DECLARE(assign_local); + /* }}} */ + + /* {{{ proto int InetSocket::bind(string address, int port); + * Bind socket to a local endpoint (AF_INET or AF_INET6). */ + template + P3_METHOD_DECLARE(bind_inet); + /* }}} */ + + /* {{{ proto int LocalSocket::bind(string path); + * Bind socket to a local endpoint (AF_UNIX). */ + template + P3_METHOD_DECLARE(bind_local); + /* }}} */ + + P3_METHOD_DECLARE(connect); + + /* {{{ proto Future StreamSocket::read(int length, [bool read_some = true], + * [callable callback], [mixed argument]); + * Read asynchronously from stream socket. */ + template , typename P::socket>> + P3_METHOD_DECLARE(read); + /* }}} */ + + /* {{{ proto Future StreamSocket::write(string data, [bool write_some = false], + * [callable callback], [mixed argument]); + * Write asynchronously to stream socket. */ + template , typename P::socket>> + P3_METHOD_DECLARE(write); + /* }}} */ + + /* {{{ proto Future DatagramSocket::recvFrom(int length, + * [callable callback], [mixed argument]); + * Receive asynchronously from datagram socket.*/ + template , typename P::socket>> + P3_METHOD_DECLARE(recvFrom); + /* }}} */ + + template , typename P::socket>> + P3_METHOD_DECLARE(sendTo); + + /* {{{ proto string InetSocket::remoteAddr(void); + * Get remote address (AF_INET or AF_INET6). */ + template + P3_METHOD_DECLARE(remoteAddr) const; + /* }}} */ + + /* {{{ proto int InetSocket::remotePort(void); + * Get remote port (AF_INET or AF_INET6). */ + template + P3_METHOD_DECLARE(remotePort) const; + /* }}} */ + + /* {{{ proto string LocalSocket::remotePath(void); + * Get socket file path of remote endpoint (AF_UNIX). */ + template + P3_METHOD_DECLARE(remotePath) const; + /* }}} */ + + /* {{{ proto int Socket::available([int &ec]); + * Determine the number of bytes available for reading. */ + P3_METHOD_DECLARE(available) const; + /* }}} */ + + /* {{{ proto bool Socket::atMark([int &ec]); + * Determine whether the socket is at the out-of-band data mark. */ + P3_METHOD_DECLARE(atMark) const; + /* }}} */ + + /* {{{ proto int Socket::cancel(void); + * Cancel all asynchronous operations on the socket. */ + P3_METHOD_DECLARE(cancel); + /* }}} */ + + /* {{{ proto int Socket::close(void); + * Close the socket. */ + P3_METHOD_DECLARE(close); + /* }}} */ + + PHP_ASIO_CE_DECLARE(); + }; + + using tcp_socket = socket; + using unix_socket = socket; + using udp_socket = socket; + using udg_socket = socket; +} diff --git a/src/strand.cpp b/src/strand.cpp new file mode 100644 index 0000000..cda8a33 --- /dev/null +++ b/src/strand.cpp @@ -0,0 +1,50 @@ +/** + * php-asio/strand.cpp + * + * @author CismonX + */ + +#include "strand.hpp" +#include "future.hpp" + +#ifdef ENABLE_STRAND + +#define STRAND_DISPATCH_CALLBACK(meth) \ + PHP_ASIO_DISPATCH_CALLBACK(meth) PHP_ASIO_DEC_HANDLER_COUNT(); }) + +namespace asio +{ + P3_METHOD(strand, dispatch) + { + PHP_ASIO_INC_HANDLER_COUNT(); + STRAND_DISPATCH_CALLBACK(strand_.dispatch); + } + + P3_METHOD(strand, post) + { + PHP_ASIO_INC_HANDLER_COUNT(); + STRAND_DISPATCH_CALLBACK(strand_.post); + } + + P3_METHOD(strand, runningInThisThread) const + { + RETVAL_BOOL(strand_.running_in_this_thread()); + } + + P3_METHOD(strand, wrap) + { + zval* callback; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(callback) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_INC_HANDLER_COUNT(); + RETVAL_OBJ(p3::alloc_object(wrapped_handler::class_entry, + [this, callback](wrapped_handler* ptr) { + new(ptr) wrapped_handler(this, callback); + })); + } + + PHP_ASIO_CE_DEFINE(strand); +} + +#endif // ENABLE_STRAND diff --git a/src/strand.hpp b/src/strand.hpp new file mode 100644 index 0000000..a0f9e71 --- /dev/null +++ b/src/strand.hpp @@ -0,0 +1,87 @@ +/** + * php-asio/strand.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +#define PHP_ASIO_DISPATCH_CALLBACK(meth) \ + zval* callback; \ + zval* argument = nullptr; \ + ZEND_PARSE_PARAMETERS_START(1, 2) \ + Z_PARAM_ZVAL(callback) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_ZVAL(argument) \ + ZEND_PARSE_PARAMETERS_END(); \ + ZVAL_PTR_INIT(cb); \ + ZVAL_COPY(cb, callback); \ + ZVAL_PTR_INIT(args); \ + if (argument) \ + ZVAL_COPY(args, argument); \ + else \ + ZVAL_NULL(args); \ + meth([this, cb, args]() { \ + INIT_RETVAL(); \ + call_user_function(CG(function_table), nullptr, cb, PASS_RETVAL, 1, args); \ + CORO_REGISTER(retval); \ + zval_ptr_dtor(cb); \ + zval_ptr_dtor(args); \ + efree(cb); \ + efree(args); \ + +#ifdef ENABLE_STRAND +namespace asio +{ + /// Wrapper for Boost.Asio strand. + /// Provides serialized handler execution. + class strand : public base + { + /// Boost.Asio strand instance. + boost::asio::strand strand_; + + public: + /// Constructor. + explicit strand( + boost::asio::io_service& io_service + ) : base(io_service), strand_(io_service) {} + + /// Deleted default constructor. + strand() = delete; + + /// Deleted copy constructor. + strand(const strand&) = delete; + + /* {{{ proto void Strand::dispatch(callable callback, [mixed argument]); + * Request the strand to invoke the given handler. */ + P3_METHOD_DECLARE(dispatch); + /* }}} */ + + /* {{{ proto void Strand::post(callable callback, [mixed argument]); + * Request the strand to invoke the given handler. */ + P3_METHOD_DECLARE(post); + /* }}} */ + + /* {{{ proto bool Strand::runningInThisThread(void); + * Determine whether the strand is running in the current thread. */ + P3_METHOD_DECLARE(runningInThisThread) const; + /* }}} */ + + /* {{{ proto WrappedHandler wrap([callable callback]); + * Create a new handler that automatically dispatches the wrapped handler on the strand. */ + P3_METHOD_DECLARE(wrap); + /* }}} */ + + /// Returns the wrapped Boost.Asio strand. + boost::asio::strand* implmentation() + { + return &strand_; + } + + PHP_ASIO_CE_DECLARE(); + }; +} +#endif // ENABLE_STRAND diff --git a/src/stream_descriptor.cpp b/src/stream_descriptor.cpp new file mode 100644 index 0000000..30511b8 --- /dev/null +++ b/src/stream_descriptor.cpp @@ -0,0 +1,86 @@ +/** + * php-asio/stream_descriptor.cpp + * + * @author CismonX + */ + +#include "stream_descriptor.hpp" +#include "future.hpp" +#include "io.hpp" + +namespace asio +{ + zval* stream_descriptor::read_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument) + { + ZSTR_VAL(buffer)[length] = '\0'; + ZSTR_LEN(buffer) = length; + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_STR(&arguments[1], buffer); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_STR, buffer); + } + + zval* stream_descriptor::write_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument) + { +#ifdef ENABLE_NULL_BUFFERS + if (buffer) +#endif //ENABLE_NULL_BUFFERS + zend_string_release(buffer); + PHP_ASIO_INVOKE_CALLBACK_START(4) + ZVAL_LONG(&arguments[1], static_cast(length)); + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN(ZVAL_LONG, static_cast(length)); + } + + P3_METHOD(stream_descriptor, assign) + { + zval* fd; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(fd) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + if (UNEXPECTED(Z_TYPE_P(fd) == IS_LONG)) + stream_descriptor_.assign(Z_LVAL_P(fd), ec); + else + stream_descriptor_.assign(resource_to_fd(fd), ec); + RETVAL_EC(ec); + } + + P3_METHOD(stream_descriptor, isOpen) + { + RETVAL_BOOL(stream_descriptor_.is_open()); + } + + P3_METHOD(stream_descriptor, read) + { + PHP_ASIO_READ(stream_descriptor, stream_descriptor_); + } + + P3_METHOD(stream_descriptor, write) + { + PHP_ASIO_WRITE(stream_descriptor, stream_descriptor_); + } + + P3_METHOD(stream_descriptor, release) + { + stream_descriptor_.release(); + } + + P3_METHOD(stream_descriptor, cancel) + { + boost::system::error_code ec; + RETVAL_EC(stream_descriptor_.cancel(ec)); + } + + P3_METHOD(stream_descriptor, close) + { + boost::system::error_code ec; + RETVAL_EC(stream_descriptor_.close(ec)); + } + + PHP_ASIO_CE_DEFINE(stream_descriptor); +} diff --git a/src/stream_descriptor.hpp b/src/stream_descriptor.hpp new file mode 100644 index 0000000..688b9b1 --- /dev/null +++ b/src/stream_descriptor.hpp @@ -0,0 +1,73 @@ +/** + * php-asio/stream_descriptor.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio stream descriptor. + class stream_descriptor : public base + { + /// Boost.Asio stream descriptor instance. + boost::asio::posix::stream_descriptor stream_descriptor_; + + /// Read handler for stream descriptor. + zval* read_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument); + + /// Write handler for socket. + zval* write_handler(const boost::system::error_code& error, + size_t length, zend_string* buffer, zval* callback, zval* argument); + + public: + /// Constructor. + explicit stream_descriptor( + boost::asio::io_service& io_service + ) : base(io_service), stream_descriptor_(io_service) {} + + /* {{{ proto int StreamDescriptor::bind(int|resource fd); + * Opens the descriptor to hold an existing native descriptor. */ + P3_METHOD_DECLARE(assign); + /* }}} */ + + /* {{{ proto bool StreamDescriptor::isOpen(void); + * Determine whether the descriptor is open. */ + P3_METHOD_DECLARE(isOpen); + /* }}} */ + + /* {{{ proto Future StreamDescriptor::read(int length, [bool read_some = true], + * [callable callback], [mixed argument]); + * Read asynchronously from stream descriptor. */ + P3_METHOD_DECLARE(read); + /* }}} */ + + /* {{{ proto Future StreamDescriptor::write(string data, [bool write_some = false], + * [callable callback], [mixed argument]); + * Write asynchronously to stream socket. */ + P3_METHOD_DECLARE(write); + /* }}} */ + + /* {{{ proto void StreamDescriptor::release(void); + * Release ownership of the native descriptor implementation. */ + P3_METHOD_DECLARE(release); + /* }}} */ + + /* {{{ proto int StreamDescriptor::cancel(void); + * Cancel all asynchronous operations on the descriptor. */ + P3_METHOD_DECLARE(cancel); + /* }}} */ + + /* {{{ proto int StreamDescriptor::close(void); + * Close the descriptor. */ + P3_METHOD_DECLARE(close); + /* }}} */ + + PHP_ASIO_CE_DECLARE(); + }; +} diff --git a/src/timer.cpp b/src/timer.cpp new file mode 100644 index 0000000..fb00cf9 --- /dev/null +++ b/src/timer.cpp @@ -0,0 +1,69 @@ +/** + * php-asio/timer.cpp + * + * @author CismonX + */ + +#include + +#include "timer.hpp" +#include "future.hpp" + +namespace asio +{ + zval* timer::handler(const boost::system::error_code& error, + zval* callback, zval* argument) + { + PHP_ASIO_INVOKE_CALLBACK_START(3) + PHP_ASIO_INVOKE_CALLBACK(); + PHP_ASIO_INVOKE_CALLBACK_END(); + CORO_RETURN_NULL(); + } + + P3_METHOD(timer, expiresFromNow) + { + zend_long duration; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(duration) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + timer_.expires_from_now(boost::posix_time::millisec(duration), ec); + RETVAL_EC(ec); + } + + P3_METHOD(timer, expiresAt) + { + zend_long timestamp; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(timestamp) + ZEND_PARSE_PARAMETERS_END(); + boost::system::error_code ec; + timer_.expires_at(boost::posix_time::from_time_t(timestamp / 1000) + + boost::posix_time::millisec(timestamp % 1000), ec); + RETVAL_EC(ec); + } + + P3_METHOD(timer, wait) + { + zval* callback = nullptr; + zval* argument = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(callback) + Z_PARAM_ZVAL(argument) + ZEND_PARSE_PARAMETERS_END(); + PHP_ASIO_FUTURE_INIT(); + future->on_resolve(boost::bind(&timer::handler, this, _1, cb, args)); + timer_.async_wait(STRAND_RESOLVE(ASYNC_HANDLER_SINGLE_ARG)); + FUTURE_RETURN(); + } + + P3_METHOD(timer, cancel) + { + boost::system::error_code ec; + timer_.cancel(ec); + RETVAL_EC(ec); + } + + PHP_ASIO_CE_DEFINE(timer); +} diff --git a/src/timer.hpp b/src/timer.hpp new file mode 100644 index 0000000..820224a --- /dev/null +++ b/src/timer.hpp @@ -0,0 +1,52 @@ +/** + * php-asio/timer.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "base.hpp" + +namespace asio +{ + /// Wrapper for Boost.Asio deadline timer. + class timer : public base + { + /// Boost.Asio timer instance. + boost::asio::deadline_timer timer_; + + /// Handler for timer callback. + zval* handler(const boost::system::error_code& error, + zval* callback, zval* argument); + + public: + /// Constructor. + explicit timer( + boost::asio::io_service& io_service + ) : base(io_service), timer_(io_service) {} + + /* {{{ proto int Timer::expiresFromNow(int duration); + * Set the timer's expiry time relative to now. */ + P3_METHOD_DECLARE(expiresFromNow); + /* }}} */ + + /* {{{ proto int Timer::expiresFromNow(int timestamp); + * Set the timer's expiry time as an absolute time. */ + P3_METHOD_DECLARE(expiresAt); + /* }}} */ + + /* {{{ proto Future Timer::wait([callable callback], [mixed argument]); + * Initiate an asynchronous wait against the timer. */ + P3_METHOD_DECLARE(wait); + /* }}} */ + + /* {{{ proto int Timer::cancel(void); + * Cancel timer. */ + P3_METHOD_DECLARE(cancel); + /* }}} */ + + PHP_ASIO_CE_DECLARE(); + }; +} \ No newline at end of file diff --git a/src/wrapped_handler.cpp b/src/wrapped_handler.cpp new file mode 100644 index 0000000..eab6fbd --- /dev/null +++ b/src/wrapped_handler.cpp @@ -0,0 +1,14 @@ +/** + * php-asio/wrapped_handler.cpp + * + * @author CismonX + */ + +#include "wrapped_handler.hpp" + +#ifdef ENABLE_STRAND +namespace asio +{ + PHP_ASIO_CE_DEFINE(wrapped_handler); +} +#endif // ENABLE_STRAND diff --git a/src/wrapped_handler.hpp b/src/wrapped_handler.hpp new file mode 100644 index 0000000..d02c87f --- /dev/null +++ b/src/wrapped_handler.hpp @@ -0,0 +1,48 @@ +/** + * php-asio/wrapped_handler.hpp + * + * @author CismonX + */ + +#pragma once + +#include "common.hpp" +#include "strand.hpp" + +#ifdef ENABLE_STRAND +namespace asio +{ + /// Async handler callback wrapped by strand. + class wrapped_handler + { + /// Strand object for this wrapped handler. + strand* strand_; + + /// Wrapped handler callback. + zval* callback_; + + // Future has access to Strand and handler callback. + friend class future; + + public: + /// Constructor. + explicit wrapped_handler( + strand* strand, zval* callback + ) : strand_(strand), callback_(callback) + { + GC_ADDREF(Z_COUNTED_P(callback_)); + } + + /// Deleted default constructor. + wrapped_handler() = delete; + + /// Deleted copy constructor. + wrapped_handler(const wrapped_handler&) = delete; + + /// Make this object callable. + P3_METHOD_DECLARE(__invoke) {} + + PHP_ASIO_CE_DECLARE(); + }; +} +#endif // ENABLE_STRAND diff --git a/stubs/Acceptor.php b/stubs/Acceptor.php new file mode 100644 index 0000000..66736c6 --- /dev/null +++ b/stubs/Acceptor.php @@ -0,0 +1,44 @@ + + */ + +namespace Asio; + +/** + * Interface Acceptor + * + * @package Asio + */ +interface Acceptor extends IoObject +{ + /** + * Put the acceptor into the state where it may accept new connections. + * + * @param int $backlog[optional] : The maximum length of the queue of pending connections. + * Default to `boost::asio::socket_base::max_connections`. + * @return int : Error code + */ + function listen(int $backlog); + + /** + * Asynchronously accept a new connection into a socket. + * + * @param callable $callback[optional] : Acceptor callback + * @param mixed $argument + * @return Future : Resolves Socket. + */ + function accept(callable $callback, $argument = null); + + /** + * Close the acceptor. + * + * @return int : Error code + */ + function close(); +} \ No newline at end of file diff --git a/stubs/DatagramSocket.php b/stubs/DatagramSocket.php new file mode 100644 index 0000000..d987161 --- /dev/null +++ b/stubs/DatagramSocket.php @@ -0,0 +1,29 @@ + + */ + +namespace Asio; + +/** + * Interface DatagramSocket + * + * @package Asio + */ +interface DatagramSocket extends Socket +{ + /** + * Receive asynchronously from datagram socket. + * + * @param int $length + * @param callable $callback[optional] : Receive handler callback + * @param mixed $argument + * @return Future + */ + function recvFrom(int $length, callable $callback, $argument = null); +} \ No newline at end of file diff --git a/stubs/Future.php b/stubs/Future.php new file mode 100644 index 0000000..c1034f3 --- /dev/null +++ b/stubs/Future.php @@ -0,0 +1,27 @@ + + */ + +namespace Asio; + +/** + * Class Future + * + * When an asynchronous operation completes, its Future will be resolved, + * resuming the corresponding coroutine (if Future was yielded by a Generator). + * + * @package Asio + */ +final class Future +{ + /** + * This class can only be instantiated by asynchronous operations. + */ + private function __construct() {} +} \ No newline at end of file diff --git a/stubs/InetSocket.php b/stubs/InetSocket.php new file mode 100644 index 0000000..c518a82 --- /dev/null +++ b/stubs/InetSocket.php @@ -0,0 +1,59 @@ + + */ + +namespace Asio; + +/** + * Interface InetSocket + * + * @package Asio + */ +interface InetSocket extends Socket +{ + /** + * Open socket. + * + * @param bool $inet6 : True for AF_INET6, false for AF_INET + * @return int : Error code + */ + function open(bool $inet6); + + /** + * Assign an existing native socket to the socket. + * + * @param bool $inet6 + * @param int|resource $native_handle + * @return int : Error code + */ + function assign(bool $inet6, $native_handle); + + /** + * Bind socket to a local endpoint. + * + * @param string $address + * @param int $port + * @return int : Error code + */ + function bind(string $address, int $port); + + /** + * Get remote endpoint address. + * + * @return string + */ + function remoteAddr(); + + /** + * Get remote endpoint port. + * + * @return mixed + */ + function remotePort(); +} \ No newline at end of file diff --git a/stubs/IoObject.php b/stubs/IoObject.php new file mode 100644 index 0000000..2bae453 --- /dev/null +++ b/stubs/IoObject.php @@ -0,0 +1,28 @@ + + */ + +namespace Asio; + +/** + * Interface IoObject + * + * Base class of all I/O objects. + * + * @package Asio + */ +interface IoObject +{ + /** + * Cancel all asynchronous operations on this I/O object. + * + * @return int : Error code + */ + function cancel(); +} \ No newline at end of file diff --git a/stubs/LocalSocket.php b/stubs/LocalSocket.php new file mode 100644 index 0000000..3440a23 --- /dev/null +++ b/stubs/LocalSocket.php @@ -0,0 +1,49 @@ + + */ + +namespace Asio; + +/** + * Interface LocalSocket + * + * @package Asio + */ +interface LocalSocket extends Socket +{ + /** + * Open socket. + * + * @return int : Error code + */ + function open(); + + /** + * Assign an existing native socket to the socket. + * + * @param int|resource $native_handle + * @return int : Error code + */ + function assign($native_handle); + + /** + * Bind socket to a local endpoint. + * + * @param string $path : Path to socket file + * @return mixed + */ + function bind(string $path); + + /** + * Get remote endpoint path. + * + * @return string + */ + function remotePath(); +} \ No newline at end of file diff --git a/stubs/Resolver.php b/stubs/Resolver.php new file mode 100644 index 0000000..b731756 --- /dev/null +++ b/stubs/Resolver.php @@ -0,0 +1,30 @@ + + */ + +namespace Asio; + +/** + * Interface Resolver + * + * @package Asio + */ +interface Resolver extends IoObject +{ + /** + * Initiate an asynchronous resolve against the resolver. + * + * @param string $host : Host name + * @param string $service + * @param callable $callback[optional] : Resolver callback + * @param mixed $argument + * @return Future + */ + function resolve(string $host, string $service = '', callable $callback, $argument = null); +} \ No newline at end of file diff --git a/stubs/Service.php b/stubs/Service.php new file mode 100644 index 0000000..e4e4c6b --- /dev/null +++ b/stubs/Service.php @@ -0,0 +1,202 @@ + + */ + +namespace Asio; + +/** + * Class Service + * + * Provide access to instantiation of I/O objects. + * + * @package Asio + */ +class Service +{ + /** + * Add a new timer. + * + * @return Timer + */ + function addTimer() {} + + /** + * Add a new TCP acceptor. + * + * @return TcpAcceptor + */ + function addTcpAcceptor() {} + + /** + * Add a new UNIX domain socket acceptor (SOCK_STREAM). + * + * @return UnixAcceptor + */ + function addUnixAcceptor() {} + + /** + * Add a TCP resolver. + * + * @return TcpResolver + */ + function addTcpResolver() {} + + /** + * Add a UDP resolver. + * + * @return UdpResolver + */ + function addUdpResolver() {} + + /** + * Add a new TCP socket. + * + * @return TcpSocket + */ + function addTcpSocket() {} + + /** + * Add a new UDP socket. + * + * @return UdpSocket + */ + function addUdpSocket() {} + + /** + * Add a new UNIX domain socket (SOCK_STREAM). + * + * @return UnixSocket + */ + function addUnixSocket() {} + + /** + * Add a new UNIX domain socket (SOCK_DGRAM). + * + * @return UdgSocket + */ + function addUdgSocket() {} + + /** + * Add new signal set. + * + * @return Signal + */ + function addSignal() {} + + /** + * Add new stream descriptor. + * + * @return StreamDescriptor + */ + function addStreamDescriptor() {} + + /** + * Add new strand. + * + * @return Strand + */ + function addStrand() {} + + /** + * Run the event loop until stopped or no more work. + * + * @param int $ec[optional] : Error code + * @return int : The number of handlers executed + */ + function run(int &$ec) {} + + /** + * Run until stopped or one operation is performed. + * + * @param int $ec[optional] : Error code + * @return int : The number of handlers executed + */ + function runOne(int &$ec) {} + + /** + * Poll for operations without blocking. + * + * @param int $ec[optional] : Error code + * @return int : The number of handlers executed + */ + function poll(int &$ec) {} + + /** + * Poll for one operation without blocking. + * + * @param int $ec[optional] : Error code + * @return int : The number of handlers executed + */ + function pollOne(int &$ec) {} + + /** + * Request invocation of the given handler and return immediately. + * + * @param callable $callback + * @param mixed $argument + * @return int : The number of handlers executed + */ + function post(callable $callback, $argument = null) {} + + /** + * Request invocation of the given handler. + * + * @param callable $callback + * @param mixed $argument + * @return int : The number of handlers executed + */ + function dispatch(callable $callback, $argument = null) {} + + /** + * Notify all services of a fork event. (prepare to fork) + * + * @return int : Error code + */ + function forkPrepare() {} + + /** + * Notify all services of a fork event. (fork parent) + * + * @return int : Error code + */ + function forkParent() {} + + /** + * Notify all services of a fork event. (fork child) + * + * @return int : Error code + */ + function forkChild() {} + + /** + * Stop the event processing loop. + * + * @return void + */ + function stop() {} + + /** + * Reset in preparation for a subsequent run invocation. + * + * @return void + */ + function reset() {} + + /** + * Determine whether the io_service is stopped. + */ + function stopped() {} + + /** + * Get last error code emitted by yielded async operations of this thread. + * + * @return int + */ + static function lastError() {} +} \ No newline at end of file diff --git a/stubs/Signal.php b/stubs/Signal.php new file mode 100644 index 0000000..dc3c00a --- /dev/null +++ b/stubs/Signal.php @@ -0,0 +1,63 @@ + + */ + +namespace Asio; + +/** + * Class Signal + * + * Provides signal handling functionality. + * + * @package Asio + */ +final class Signal implements IoObject +{ + /** + * This class can only be instantiated using `Service::addSignal()`. + */ + private function __construct() {} + + /** + * Add the specified signal(s) to the signal set. + * + * @param int[] ...$signals[optional] + * @return int : Error code + */ + function add(int... $signals) {} + + /** + * Initiate an asynchronous wait against the signal set. + * + * @param callable $callback[optional] + * @param mixed $argument + * @return Future + */ + function wait(callable $callback, $argument = null) {} + + /** + * Remove the specified signal(s) from the signal set. + * + * @param int[] ...$signals[optional] + * @return int : Error code + */ + function remove(int... $signals) {} + + /** + * Remove all signals from the signal set. + * + * @return int : Error code + */ + function clear() {} + + /** + * {@inheritdoc} + */ + function cancel() {} +} \ No newline at end of file diff --git a/stubs/Socket.php b/stubs/Socket.php new file mode 100644 index 0000000..d03957e --- /dev/null +++ b/stubs/Socket.php @@ -0,0 +1,42 @@ + + */ + +namespace Asio; + +/** + * Interface Socket + * + * @package Asio + */ +interface Socket extends IoObject +{ + /** + * Determine the number of bytes available for reading. + * + * @param int $ec[optional] : Error code + * @return int + */ + function available(int &$ec); + + /** + * Determine whether the socket is at the out-of-band data mark. + * + * @param int $ec[optional] : Error code + * @return bool + */ + function atMark(int &$ec); + + /** + * Close socket. + * + * @return int : Error code + */ + function close(); +} \ No newline at end of file diff --git a/stubs/Strand.php b/stubs/Strand.php new file mode 100644 index 0000000..d360ace --- /dev/null +++ b/stubs/Strand.php @@ -0,0 +1,59 @@ + + */ + +namespace Asio; + +/** + * Class Strand + * + * Provides serialized handler execution. + * + * @package Asio + */ +final class Strand +{ + /** + * This class can only be instantiated using `Service::addStrand()`. + */ + private function __construct() {} + + /** + * Request the strand to invoke the given handler. + * + * @param callable $callback + * @param mixed $argument + * @return void + */ + function dispatch(callable $callback, $argument = null) {} + + /** + * Request the strand to invoke the given handler and return immediately. + * + * @param callable $callback + * @param mixed $argument + * @return void + */ + function post(callable $callback, $argument = null) {} + + /** + * Determine whether the strand is running in the current thread. + * + * @return bool + */ + function runningInThisThread() {} + + /** + * Create a new handler that automatically dispatches the wrapped handler on the strand. + * + * @param callable $callback[optional] + * @return WrappedHandler + */ + function wrap(callable $callback) {} +} \ No newline at end of file diff --git a/stubs/StreamDescriptor.php b/stubs/StreamDescriptor.php new file mode 100644 index 0000000..b541d1d --- /dev/null +++ b/stubs/StreamDescriptor.php @@ -0,0 +1,75 @@ + + */ + +namespace Asio; + +/** + * Class StreamDescriptor + * + * Provides stream-oriented descriptor functionality. + * + * @package Asio + */ +final class StreamDescriptor implements IoObject +{ + /** + * This class can only be instantiated using `Service::addStreamDescriptor()`. + */ + private function __construct() {} + + /** + * Opens the descriptor to hold an existing native descriptor. + * + * @param int|resource $native_handle + * @return int : Error code + */ + function assign($native_handle) {} + + /** + * Determine whether the descriptor is open. + * + * @return bool + */ + function isOpen() {} + + /** + * Read asynchronously from stream descriptor. + * + * @param int $length : Max number of bytes to be read + * @param bool $read_some + * @param callable $callback[optional] : Read handler callback + * @param mixed $argument + * @return Future : Resolves received data(string) + */ + function read(int $length, bool $read_some = true, callable $callback, $argument = null) {} + + /** + * Write asynchronously to stream descriptor. + * + * @param string $data : Write buffer + * @param bool $write_some + * @param callable $callback[optional] : Write handler callback + * @param mixed $argument + * @return Future : Resolves bytes transferred(int) + */ + function write(string $data, bool $write_some = false, callable $callback, $argument = null) {} + + /** + * Release ownership of the native descriptor implementation. + * + * @return void + */ + function release() {} + + /** + * {@inheritdoc} + */ + function cancel() {} +} \ No newline at end of file diff --git a/stubs/StreamSocket.php b/stubs/StreamSocket.php new file mode 100644 index 0000000..4838f51 --- /dev/null +++ b/stubs/StreamSocket.php @@ -0,0 +1,41 @@ + + */ + +namespace Asio; + +/** + * Interface StreamSocket + * + * @package Asio + */ +interface StreamSocket extends Socket +{ + /** + * Read asynchronously from stream socket. + * + * @param int $length : Max number of bytes to be read + * @param bool $read_some + * @param callable $callback[optional] : Read handler callback + * @param mixed $argument + * @return Future : Resolves received data(string) + */ + function read(int $length, bool $read_some = true, callable $callback, $argument = null); + + /** + * Write asynchronously to stream socket. + * + * @param string $data : Write buffer + * @param bool $write_some + * @param callable $callback[optional] : Write handler callback + * @param mixed $argument + * @return Future : Resolves bytes transferred(int) + */ + function write(string $data, bool $write_some = false, callable $callback, $argument = null); +} \ No newline at end of file diff --git a/stubs/TcpAcceptor.php b/stubs/TcpAcceptor.php new file mode 100644 index 0000000..36e93f2 --- /dev/null +++ b/stubs/TcpAcceptor.php @@ -0,0 +1,72 @@ + + */ + +namespace Asio; + +/** + * Class TcpAcceptor + * + * Used for accepting new TCP connections. + * + * @package Asio + */ +final class TcpAcceptor implements Acceptor +{ + /** + * This class can only be instantiated using `Service::addTcpAcceptor()`. + */ + private function __construct() {} + + /** + * Open acceptor. + * + * @param bool $use_ipv6 : True for AF_INET6, false for AF_INET + * @return int : Error code + */ + function open(bool $use_ipv6) {} + + /** + * Assign an existing native socket to the acceptor. + * + * @param bool $inet6 + * @param int|resource $native_handle + * @return int : Error code + */ + function assign(bool $inet6, $native_handle) {} + + /** + * Bind acceptor to local endpoint. + * + * @param string $address + * @param int $port + * @return int : Error code + */ + function bind(string $address, int $port) {} + + /** + * {@inheritdoc} + */ + function listen(int $backlog = null) {} + + /** + * {@inheritdoc} + */ + function accept(callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/TcpResolver.php b/stubs/TcpResolver.php new file mode 100644 index 0000000..6b98fe6 --- /dev/null +++ b/stubs/TcpResolver.php @@ -0,0 +1,37 @@ + + */ + +namespace Asio; + +/** + * Class TcpResolver + * + * Provides the ability to resolve a query to an array of endpoints. + * Remote service should use TCP as transport protocol. + * + * @package Asio + */ +final class TcpResolver implements Resolver +{ + /** + * This class can only be instantiated using `Service::addTcpResolver()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function resolve(string $host, string $service = '', callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} +} \ No newline at end of file diff --git a/stubs/TcpSocket.php b/stubs/TcpSocket.php new file mode 100644 index 0000000..5b50868 --- /dev/null +++ b/stubs/TcpSocket.php @@ -0,0 +1,91 @@ + + */ + +namespace Asio; + +/** + * Class TcpSocket + * + * Provides operations on TCP sockets. + * + * @package Asio + */ +final class TcpSocket implements StreamSocket, InetSocket +{ + /** + * This class can only be instantiated using `Service::addTcpSocket()` and `TcpAcceptor::Accept()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function open(bool $inet6) {} + + /** + * {@inheritdoc} + */ + function assign(bool $inet6, $native_handle) {} + + /** + * {@inheritdoc} + */ + function bind(string $address, int $port) {} + + /** + * Connect to a remote endpoint. + * + * @param string $address : Remote address + * @param int $port : Remote port + * @param callable $callback[optional] + * @param mixed $argument + */ + function connect(string $address, int $port, callable $callback, $argument = null) {} + + /** + * {@inheritdoc} + */ + function read(int $length, bool $read_some = true, callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function write(string $data, bool $write_some = false, callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function remoteAddr() {} + + /** + * {@inheritdoc} + */ + function remotePort() {} + + /** + * {@inheritdoc} + */ + function available(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function atMark(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/Timer.php b/stubs/Timer.php new file mode 100644 index 0000000..7bb327d --- /dev/null +++ b/stubs/Timer.php @@ -0,0 +1,54 @@ + + */ + +namespace Asio; + +/** + * Wrapper for Boost.Asio deadline_timer. + * + * @package Asio + */ +final class Timer implements IoObject +{ + /** + * This class can only be instantiated using "Service::addTimer()". + */ + private function __construct() {} + + /** + * Set the timer's expiry time relative to now. + * + * @param int $duration : Milliseconds from now. + * @return int : Error code. + */ + function expiresFromNow(int $duration) {} + + /** + * Set the timer's expiry time as an absolute time. + * + * @param int $timestamp : UNIX timestamp(in milliseconds). + * @return int : Error code. + */ + function expiresAt(int $timestamp) {} + + /** + * Initiate an asynchronous wait against the timer. + * + * @param callable $callback[optional] + * @param mixed $argument + * @return Future : Resolves null + */ + function wait(callable $callback, $argument = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} +} \ No newline at end of file diff --git a/stubs/UdgSocket.php b/stubs/UdgSocket.php new file mode 100644 index 0000000..d0ec8e2 --- /dev/null +++ b/stubs/UdgSocket.php @@ -0,0 +1,82 @@ + + */ + +namespace Asio; + +/** + * Class UdgSocket + * + * + * @package Asio + */ +final class UdgSocket implements DatagramSocket, LocalSocket +{ + /** + * This class can only be instantiated using `Service::addUdgSocket()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function open() {} + + /** + * {@inheritdoc} + */ + function assign($native_handle) {} + + /** + * {@inheritdoc} + */ + function bind(string $path) {} + + /** + * {@inheritdoc} + */ + function recvFrom(int $length, callable $callback = null, $argument = null) {} + + /** + * Send asynchronously to UNIX datagram socket. + * + * @param string $data : Data to send to datagram socket. + * @param string $path : Socket path of remote endpoint. + * @param callable $callback[optional] + * @param mixed $argument + * @return Future + */ + function sendTo(string $data, string $path, callable $callback, $argument = null) {} + + /** + * {@inheritdoc} + */ + function remotePath() {} + + /** + * {@inheritdoc} + */ + function available(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function atMark(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/UdpResolver.php b/stubs/UdpResolver.php new file mode 100644 index 0000000..be4c0e5 --- /dev/null +++ b/stubs/UdpResolver.php @@ -0,0 +1,37 @@ + + */ + +namespace Asio; + +/** + * Class UdpResolver + * + * Provides the ability to resolve a query to an array of endpoints. + * Remote service should use UDP as transport protocol. + * + * @package Asio + */ +final class UdpResolver implements Resolver +{ + /** + * This class can only be instantiated using `Service::addTcpResolver()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function resolve(string $host, string $service = '', callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} +} \ No newline at end of file diff --git a/stubs/UdpSocket.php b/stubs/UdpSocket.php new file mode 100644 index 0000000..1da40a3 --- /dev/null +++ b/stubs/UdpSocket.php @@ -0,0 +1,89 @@ + + */ + +namespace Asio; + +/** + * Class UdpSocket + * + * Provides operations on UDP sockets. + * + * @package Asio + */ +final class UdpSocket implements InetSocket, DatagramSocket +{ + /** + * This class can only be instantiated using `Service::addUdpSocket()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function open(bool $inet6) {} + + /** + * {@inheritdoc} + */ + function assign(bool $inet6, $native_handle) {} + + /** + * {@inheritdoc} + */ + function bind(string $address, int $port) {} + + /** + * {@inheritdoc} + */ + function recvFrom(int $length, callable $callback = null, $argument = null) {} + + /** + * Send asynchronously to UNIX datagram socket. + * + * @param string $data : Data to send to datagram socket. + * @param string $address : Remote address + * @param int $port : Remote port + * @param callable $callback[optional] + * @param mixed $argument + * @return Future + */ + function sendTo(string $data, string $address, int $port, callable $callback, $argument = null) {} + + /** + * {@inheritdoc} + */ + function remoteAddr() {} + + /** + * {@inheritdoc} + */ + function remotePort() {} + + /** + * {@inheritdoc} + */ + function available(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function atMark(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/UnixAcceptor.php b/stubs/UnixAcceptor.php new file mode 100644 index 0000000..7010ee7 --- /dev/null +++ b/stubs/UnixAcceptor.php @@ -0,0 +1,69 @@ + + */ + +namespace Asio; + +/** + * Class UnixAcceptor + * + * Used for accepting new UNIX domain socket connections. + * + * @package Asio + */ +final class UnixAcceptor implements Acceptor +{ + /** + * This class can only be instantiated using `Service::addUnixAcceptor()`. + */ + private function __construct() {} + + /** + * Open acceptor. + * + * @return int : Error code. + */ + function open() {} + + /** + * Assign an existing native socket to the acceptor. + * + * @param int|resource $native_handle + * @return int : Error code + */ + function assign($native_handle) {} + + /** + * Bind acceptor to local endpoint. + * + * @param string $path + * @return int : Error code. + */ + function bind(string $path) {} + + /** + * {@inheritdoc} + */ + function listen(int $backlog = null) {} + + /** + * {@inheritdoc} + */ + function accept(callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/UnixSocket.php b/stubs/UnixSocket.php new file mode 100644 index 0000000..8be6785 --- /dev/null +++ b/stubs/UnixSocket.php @@ -0,0 +1,86 @@ + + */ + +namespace Asio; + +/** + * Class UnixSocket + * + * Provides operations on UNIX domain sockets (SOCK_STREAM). + * + * @package Asio + */ +final class UnixSocket implements StreamSocket, LocalSocket +{ + /** + * This class can only be instantiated using `Service::addUnixSocket()` and `UnixAcceptor::Accept()`. + */ + private function __construct() {} + + /** + * {@inheritdoc} + */ + function open() {} + + /** + * {@inheritdoc} + */ + function assign($native_handle) {} + + /** + * {@inheritdoc} + */ + function bind(string $path) {} + + /** + * Connect to a remote endpoint. + * + * @param string $path : Remote socket file path + * @param callable $callback[optional] + * @param mixed $argument + * @return int : Error code + */ + function connect(string $path, callable $callback, $argument = null) {} + + /** + * {@inheritdoc} + */ + function read(int $length, bool $read_some = true, callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function write(string $data, bool $write_some = false, callable $callback = null, $argument = null) {} + + /** + * {@inheritdoc} + */ + function remotePath() {} + + /** + * {@inheritdoc} + */ + function available(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function atMark(int &$ec = null) {} + + /** + * {@inheritdoc} + */ + function cancel() {} + + /** + * {@inheritdoc} + */ + function close() {} +} \ No newline at end of file diff --git a/stubs/WrappedHandler.php b/stubs/WrappedHandler.php new file mode 100644 index 0000000..50eabcd --- /dev/null +++ b/stubs/WrappedHandler.php @@ -0,0 +1,33 @@ + + */ + +namespace Asio; + +/** + * Class WrappedHandler + * + * Async handler callback wrapped by strand. + * + * @package Asio + */ +final class WrappedHandler +{ + /** + * This class can only be instantiated using `Strand::wrap()`. + */ + private function __construct() {} + + /** + * Making object callable. + * + * Note: Calling directly has no effect. + */ + function __invoke() {} +} \ No newline at end of file diff --git a/tests/00-load.phpt b/tests/00-load.phpt new file mode 100644 index 0000000..51f297c --- /dev/null +++ b/tests/00-load.phpt @@ -0,0 +1,8 @@ +--TEST-- +Check for whether php-asio is loaded. +--FILE-- + +--EXPECT-- diff --git a/tests/01-service.phpt b/tests/01-service.phpt new file mode 100644 index 0000000..4642ab8 --- /dev/null +++ b/tests/01-service.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test for `Service`. +--FILE-- +dispatch(function () use ($service) { + echo 'Service::dispatch()', PHP_EOL; + $service->post(function ($arg) use ($service) { + echo "Service::post($arg)"; + }, 'foo'); +}); +$service->run($ec); +if ($ec) + echo 'Error on Service::run(). ', posix_strerror($ec); +?> +--EXPECT-- +Service::dispatch() +Service::post(foo) diff --git a/tests/02-timer.phpt b/tests/02-timer.phpt new file mode 100644 index 0000000..a74e25d --- /dev/null +++ b/tests/02-timer.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for `Timer`. +--FILE-- +addTimer(); +if ($ec = $timer->expiresFromNow(200)) + die('Timer::expire() failed. '.posix_strerror($ec)); +$start_time = microtime(true); +$timer->wait(function ($timer, $ec) use ($start_time) { + if ($ec) { + echo 'Error on Timer::wait(). ', posix_strerror($ec); + return; + } + $end_time = microtime(true); + $duration = intval(1000 * ($end_time - $start_time)); + if ($duration != 200) + echo "Bad duration. Expected 200ms, got ${duration}ms"; +}); +$service->run($ec); +?> +--EXPECT-- diff --git a/tests/03-signal.phpt b/tests/03-signal.phpt new file mode 100644 index 0000000..8159d3b --- /dev/null +++ b/tests/03-signal.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test for `Signal`. +--FILE-- +addSignal(); +if ($ec = $signal->add(SIGINT)) + die('Signal::add() failed. '.posix_strerror($ec)); +$signal->wait(function ($signal, $sig_num, $ec) use ($service) { + if ($ec) { + echo 'Error on Timer::wait(). ', posix_strerror($ec); + return; + } + echo $sig_num; +}); +$service->post(function () { + if (!posix_kill(posix_getpid(), SIGINT)) + exit; +}); +$service->run(); +?> +--EXPECT-- +2 diff --git a/tests/04-socket-bind-open.phpt b/tests/04-socket-bind-open.phpt new file mode 100644 index 0000000..a68a22c --- /dev/null +++ b/tests/04-socket-bind-open.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test for binding socket to an endpoint and opening socket. +--ENV-- +SOCK_FILE=/tmp/test-asio-socket-bind.sock +--SKIPIF-- + +--FILE-- +addTcpSocket(); +if ($ec = $socket->open(false)) + die('TcpSocket::open() failed. '.posix_strerror($ec)); +if ($ec = $socket->bind('127.0.0.1', 21348)) + die('TcpSocket::bind() failed. '.posix_strerror($ec)); +$socket->close(); + +$socket = $service->addUdpSocket(); +if ($ec = $socket->open(false)) + die('UdpSocket::open() failed. '.posix_strerror($ec)); +if ($ec = $socket->bind('127.0.0.1', 21348)) + die('UdpSocket::bind() failed. '.posix_strerror($ec)); +$socket->close(); + +$socket = $service->addUnixSocket(); +if ($ec = $socket->open()) + die('UnixSocket::open() failed. '.posix_strerror($ec)); +if ($ec = $socket->bind(getenv('SOCK_FILE'))) + die('UnixSocket::bind() failed. '.posix_strerror($ec)); +$socket->close(); + +$socket = $service->addUdgSocket(); +if ($ec = $socket->open()) + die('UdgSocket::open() failed. '.posix_strerror($ec)); +if ($ec = $socket->bind(getenv('SOCK_FILE'))) + die('UdgSocket::bind() failed. '.posix_strerror($ec)); +$socket->close(); +?> +--CLEAN-- + +--EXPECT-- diff --git a/tests/05-acceptor.phpt b/tests/05-acceptor.phpt new file mode 100644 index 0000000..83d082a --- /dev/null +++ b/tests/05-acceptor.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test for `Acceptor`. +--ENV-- +SOCK_FILE=/tmp/test-asio-socket-bind.sock +--SKIPIF-- + +--FILE-- +addTcpAcceptor(); +if ($ec = $acceptor->open(false)) + die('TcpAcceptor::open() failed. '.posix_strerror($ec)); +if ($ec = $acceptor->bind('127.0.0.1', 21348)) + die('TcpAcceptor::bind() failed. '.posix_strerror($ec)); +if ($ec = $acceptor->listen()) + die('TcpAcceptor::listen() failed. '.posix_strerror($ec)); +$acceptor->accept(function ($acceptor, $socket, $ec) use ($service) { + if ($ec) { + echo 'Error on TcpAcceptor::accept. ', posix_strerror($ec); + return; + } + echo 'Accepted', PHP_EOL; + $service->post(function () use ($service) { + $acceptor = $service->addUnixAcceptor(); + if ($ec = $acceptor->open()) + die('UnixAcceptor::open() failed. '.posix_strerror($ec)); + if ($ec = $acceptor->bind(getenv('SOCK_FILE'))) + die('UnixAcceptor::bind() failed. '.posix_strerror($ec)); + if ($ec = $acceptor->listen()) + die('UnixAcceptor::listen() failed. '.posix_strerror($ec)); + $acceptor->accept(function ($acceptor, $socket, $ec) { + if ($ec) { + echo 'Error on UnixAcceptor::accept. ', posix_strerror($ec); + return; + } + echo 'Accepted'; + }); + $service->post(function () use ($service) { + if ($handle = stream_socket_client('unix://'.getenv('SOCK_FILE'))) + fclose($handle); + else + exit; + }); + }); +}); +$service->post(function () use ($service) { + if ($handle = stream_socket_client('tcp://127.0.0.1:21348')) + fclose($handle); + else + exit; +}); +$service->run(); +?> +--CLEAN-- + +--EXPECT-- +Accepted +Accepted diff --git a/tests/06-sendto-recvfrom.phpt b/tests/06-sendto-recvfrom.phpt new file mode 100644 index 0000000..ede3925 --- /dev/null +++ b/tests/06-sendto-recvfrom.phpt @@ -0,0 +1,53 @@ +--TEST-- +Test for `DatagramSocket::sendTo()` and `DatagramSocket::recvFrom()`. +--ENV-- +SOCK_FILE_1=/tmp/test-asio-socket-bind-1.sock +SOCK_FILE_2=/tmp/test-asio-socket-bind-2.sock +--FILE-- +post(function () use ($service) { + $socket = $service->addUdgSocket(); + $socket->open(); + $socket->bind(getenv('SOCK_FILE_1')); + $socket->recvFrom(100, + function ($socket, $data, $remote_path, $ec) { + if ($ec) { + echo 'UdgSocket::recvFrom() failed. ', posix_strerror($ec); + $socket->close(); + return; + } + echo $data; + $socket->sendTo('world', $remote_path, function ($socket, $length, $ec) { + if ($ec) { + echo 'UdgSocket::sendTo() failed. ', posix_strerror($ec); + $socket->close(); + return; + } + $socket->close(); + }); + }); + $socket = $service->addUdgSocket(); + $socket->open(); + $socket->bind(getenv('SOCK_FILE_2')); + $socket->sendTo('hello', getenv('SOCK_FILE_1'), function ($socket) { + $socket->recvFrom(100, function ($socket, $data, $remote_path, $ec) { + if ($ec) { + echo 'UdgSocket::recvFrom() failed. ', posix_strerror($ec); + $socket->close(); + return; + } + echo $data; + $socket->close(); + }); + }); +}); +$service->run(); +?> +--CLEAN-- + +--EXPECT-- +helloworld diff --git a/tests/07-future.phpt b/tests/07-future.phpt new file mode 100644 index 0000000..2a213c9 --- /dev/null +++ b/tests/07-future.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for `Future` and `Service::lastError()`. +--SKIPIF-- + +--FILE-- +post(function () use ($service) { + $signal = $service->addSignal(); + $signal->add(SIGINT); + $service->post(function () { + posix_kill(posix_getpid(), SIGINT); + }); + echo yield $signal->wait(); +}); +$service->run(); +?> +--EXPECT-- +2 diff --git a/tests/08-assign-read-write.phpt b/tests/08-assign-read-write.phpt new file mode 100644 index 0000000..cc76c9e --- /dev/null +++ b/tests/08-assign-read-write.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test for `assign()` on `Socket`, `Acceptor` and `StreamDescriptor`. +Test for `StreamSocket::read()` and `StreamSocket::write()`. +--SKIPIF-- + +--FILE-- +addTcpAcceptor(); +if ($ec = $acceptor->assign(false, $handle)) + die('Acceptor::assign() failed. '.posix_strerror($ec)); +$acceptor->accept(function ($acceptor, $socket, $ec) use ($service) { + if ($ec) { + echo 'Error on TcpAcceptor::accept(). ', posix_strerror($ec); + return; + } + $socket->read(5, false, function ($socket, $data, $ec) use ($service) { + if ($ec) { + echo 'TcpSocket::read() failed.', posix_strerror($ec); + return; + } + echo $data; + $stream_descriptor = $service->addStreamDescriptor(); + $stream_descriptor->assign(STDOUT); + $stream_descriptor->write('world'); + }); +}); +$service->post(function () use ($service) { + $handle = stream_socket_client('tcp://127.0.0.1:21348'); + $socket = $service->addTcpSocket(); + $socket->assign(false, $handle); + $socket->write('hello'); +}); +$service->run(); +?> +--EXPECT-- +helloworld diff --git a/tests/09-resolver.phpt b/tests/09-resolver.phpt new file mode 100644 index 0000000..f076b78 --- /dev/null +++ b/tests/09-resolver.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test for `Resolver`. +--FILE-- +post(function () use ($service) { + $resolver = $service->addTcpResolver(); + $resolver->resolve('github.com', 'http', function ($resolver, $arr, $ec) { + if ($ec = Asio\Service::lastError()) { + echo 'Resolver::resolve() failed. ', posix_strerror($ec); + return; + } + echo boolval(count($arr)); + }); +}); +$service->run(); +?> +--EXPECT-- +1 diff --git a/tests/10-fork.phpt b/tests/10-fork.phpt new file mode 100644 index 0000000..a04f12a --- /dev/null +++ b/tests/10-fork.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test for forking. +--FILE-- +forkPrepare()) + die('Service::forkParent() failed. '. posix_strerror($ec)); + if (pcntl_fork()) { + if ($ec = $service->forkParent()) + die('Service::forkParent() failed. '. posix_strerror($ec)); + $signal->wait(function () { + if (pcntl_wait($status, WUNTRACED) < 1) + die('An error occurred during wait().'); + echo $status; + }); + echo 'parent '; + } else { + if ($ec = $service->forkChild()) + die('Service::forkParent() failed. '. posix_strerror($ec)); + $signal->cancel(); + $timer = $service->addTimer(); + $timer->expiresFromNow(100); + $timer->wait($timer_cb = function ($timer) use (&$timer_cb) { + echo 'child '; + }); + } +} +$service->post(function () use ($service) { + $signal = $service->addSignal(); + $signal->add(SIGCHLD); + fork_worker($service, $signal); +}); +$service->run(); +?> +--EXPECT-- +parent child 0 diff --git a/tests/11-strand.phpt b/tests/11-strand.phpt new file mode 100644 index 0000000..4a62041 --- /dev/null +++ b/tests/11-strand.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test for `Strand`. +--SKIPIF-- + +--FILE-- +addStrand(); +$strand->post(function () use ($service, $strand) { + $timer = $service->addTimer(); + $timer->expiresFromNow(100); + $timer->wait($strand->wrap(function () { + echo 'ok'; + })); +}); +$service->run(); +?> +--EXPECT-- +ok