From 780f069d10b120968ae5867cc7e5a0da1ed05054 Mon Sep 17 00:00:00 2001 From: Benoit Germain <benoit.germain@ubisoft.com> Date: Fri, 14 Jun 2024 17:41:05 +0200 Subject: Boyscouting --- src/linda.cpp | 741 ++++++++++++++++++++++++++------------------------- src/lindafactory.cpp | 3 +- src/nameof.cpp | 1 - src/state.cpp | 14 +- src/tools.cpp | 12 +- src/tools.h | 2 +- 6 files changed, 398 insertions(+), 375 deletions(-) diff --git a/src/linda.cpp b/src/linda.cpp index 1933b06..4edc029 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -38,75 +38,86 @@ THE SOFTWARE. #include "tools.h" // ################################################################################################# +// ################################################################################################# +namespace { + // ############################################################################################# + // ############################################################################################# -static void check_key_types(lua_State* const L_, int const start_, int const end_) -{ - for (int const _i : std::ranges::iota_view{ start_, end_ + 1 }) { - switch (LuaType const _t{ luaG_type(L_, _i) }) { - case LuaType::BOOLEAN: - case LuaType::NUMBER: - case LuaType::STRING: - break; - case LuaType::LIGHTUSERDATA: - { - static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; - for (UniqueKey const& _key : kKeysToCheck) { - if (_key.equals(L_, _i)) { - raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, _key.debugName.data()); - break; + static void CheckKeyTypes(lua_State* const L_, int const start_, int const end_) + { + for (int const _i : std::ranges::iota_view{ start_, end_ + 1 }) { + switch (LuaType const _t{ luaG_type(L_, _i) }) { + case LuaType::BOOLEAN: + case LuaType::NUMBER: + case LuaType::STRING: + break; + + case LuaType::LIGHTUSERDATA: + { + static constexpr std::array<std::reference_wrapper<UniqueKey const>, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; + for (UniqueKey const& _key : kKeysToCheck) { + if (_key.equals(L_, _i)) { + raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, _key.debugName.data()); + break; + } } } + break; + + default: + raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); } - break; + } + } - default: - raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); + // ############################################################################################# + + /* + * string = linda:__tostring( linda_ud) + * + * Return the stringification of a linda + * + * Useful for concatenation or debugging purposes + */ + + template <bool OPT> + [[nodiscard]] static int LindaToString(lua_State* const L_, int const idx_) + { + Linda* const _linda{ ToLinda<OPT>(L_, idx_) }; + if (_linda != nullptr) { + luaG_pushstring(L_, "Linda: "); + std::string_view const _lindaName{ _linda->getName() }; + if (!_lindaName.empty()) { + luaG_pushstring(L_, _lindaName); + } else { + // obfuscate the pointer so that we can't read the value with our eyes out of a script + luaG_pushstring(L_, "%p", _linda->obfuscated()); + } + lua_concat(L_, 2); + return 1; } + return 0; } -} -// ################################################################################################# + // ############################################################################################# -/* - * string = linda:__tostring( linda_ud) - * - * Return the stringification of a linda - * - * Useful for concatenation or debugging purposes - */ - -template <bool OPT> -[[nodiscard]] static int LindaToString(lua_State* L_, int idx_) -{ - Linda* const _linda{ ToLinda<OPT>(L_, idx_) }; - if (_linda != nullptr) { - luaG_pushstring(L_, "Linda: "); - std::string_view const _lindaName{ _linda->getName() }; - if (!_lindaName.empty()) { - luaG_pushstring(L_, _lindaName); - } else { - // obfuscate the pointer so that we can't read the value with our eyes out of a script - luaG_pushstring(L_, "%p", _linda->obfuscated()); + template <bool OPT> + [[nodiscard]] static inline Linda* ToLinda(lua_State* const L_, int const idx_) + { + Linda* const _linda{ static_cast<Linda*>(LindaFactory::Instance.toDeep(L_, idx_)) }; + if constexpr (!OPT) { + luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr + LUA_ASSERT(L_, _linda->U == Universe::Get(L_)); } - lua_concat(L_, 2); - return 1; + return _linda; } - return 0; -} + // ############################################################################################# + // ############################################################################################# +} // namespace +// ################################################################################################# // ################################################################################################# - -template <bool OPT> -[[nodiscard]] static inline Linda* ToLinda(lua_State* L_, int idx_) -{ - Linda* const _linda{ static_cast<Linda*>(LindaFactory::Instance.toDeep(L_, idx_)) }; - if constexpr (!OPT) { - luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr - LUA_ASSERT(L_, _linda->U == Universe::Get(L_)); - } - return _linda; -} // ################################################################################################# // ################################################################################################# @@ -114,7 +125,7 @@ template <bool OPT> // ################################################################################################# // ################################################################################################# -Linda::Linda(Universe* U_, LindaGroup group_, std::string_view const& name_) +Linda::Linda(Universe* const U_, LindaGroup const group_, std::string_view const& name_) : DeepPrelude{ LindaFactory::Instance } , U{ U_ } , keeperIndex{ group_ % U_->keepers.getNbKeepers() } @@ -170,7 +181,7 @@ std::string_view Linda::getName() const // ################################################################################################# // used to perform all linda operations that access keepers -int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) +int Linda::ProtectedCall(lua_State* const L_, lua_CFunction const f_) { Linda* const _linda{ ToLinda<false>(L_, 1) }; @@ -344,14 +355,16 @@ LUAG_FUNC(linda_concat) */ LUAG_FUNC(linda_count) { - auto _count = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - // make sure the keys are of a valid type - check_key_types(L_, 2, lua_gettop(L_)); - - Keeper* const _keeper{ _linda->whichKeeper() }; - KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, 2) }; - return OptionalValue(_pushed, L_, "tried to count an invalid key"); + static constexpr lua_CFunction _count{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + // make sure the keys are of a valid type + CheckKeyTypes(L_, 2, lua_gettop(L_)); + + Keeper* const _keeper{ _linda->whichKeeper() }; + KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, 2) }; + return OptionalValue(_pushed, L_, "tried to count an invalid key"); + } }; return Linda::ProtectedCall(L_, _count); } @@ -383,9 +396,11 @@ LUAG_FUNC(linda_deep) */ LUAG_FUNC(linda_dump) { - auto _dump = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - return Keeper::PushLindaStorage(*_linda, DestState{ L_ }); + static constexpr lua_CFunction _dump{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + return Keeper::PushLindaStorage(*_linda, DestState{ L_ }); + } }; return Linda::ProtectedCall(L_, _dump); } @@ -399,28 +414,30 @@ LUAG_FUNC(linda_dump) */ LUAG_FUNC(linda_get) { - auto get = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - lua_Integer const _count{ luaL_optinteger(L_, 3, 1) }; - luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1"); - luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments"); - // make sure the key is of a valid type (throws an error if not the case) - check_key_types(L_, 2, 2); - - KeeperCallResult _pushed; - if (_linda->cancelRequest == CancelRequest::None) { - Keeper* const _keeper{ _linda->whichKeeper() }; - _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, 2); - } else { // linda is cancelled - // do nothing and return nil,lanes.cancel_error - lua_pushnil(L_); - kCancelError.pushKey(L_); - _pushed.emplace(2); + static constexpr lua_CFunction _get{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + lua_Integer const _count{ luaL_optinteger(L_, 3, 1) }; + luaL_argcheck(L_, _count >= 1, 3, "count should be >= 1"); + luaL_argcheck(L_, lua_gettop(L_) <= 3, 4, "too many arguments"); + // make sure the key is of a valid type (throws an error if not the case) + CheckKeyTypes(L_, 2, 2); + + KeeperCallResult _pushed; + if (_linda->cancelRequest == CancelRequest::None) { + Keeper* const _keeper{ _linda->whichKeeper() }; + _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, 2); + } else { // linda is cancelled + // do nothing and return nil,lanes.cancel_error + lua_pushnil(L_); + kCancelError.pushKey(L_); + _pushed.emplace(2); + } + // an error can be raised if we attempt to read an unregistered function + return OptionalValue(_pushed, L_, "tried to copy unsupported types"); } - // an error can be raised if we attempt to read an unregistered function - return OptionalValue(_pushed, L_, "tried to copy unsupported types"); }; - return Linda::ProtectedCall(L_, get); + return Linda::ProtectedCall(L_, _get); } // ################################################################################################# @@ -434,35 +451,37 @@ LUAG_FUNC(linda_get) */ LUAG_FUNC(linda_limit) { - auto _limit = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - // make sure we got 3 arguments: the linda, a key and a limit - int const _nargs{ lua_gettop(L_) }; - luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); - // make sure we got a numeric limit - lua_Integer const _limit{ luaL_optinteger(L_, 3, 0) }; - if (_limit < 0) { - raise_luaL_argerror(L_, 3, "limit must be >= 0"); - } - // make sure the key is of a valid type - check_key_types(L_, 2, 2); - - KeeperCallResult _pushed; - if (_linda->cancelRequest == CancelRequest::None) { - Keeper* const _keeper{ _linda->whichKeeper() }; - _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2); - LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, -1) == LuaType::BOOLEAN); // no error, boolean value saying if we should wake blocked writer threads - if (lua_toboolean(L_, -1)) { - _linda->readHappened.notify_all(); // To be done from within the 'K' locking area + static constexpr lua_CFunction _limit{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + // make sure we got 3 arguments: the linda, a key and a limit + int const _nargs{ lua_gettop(L_) }; + luaL_argcheck(L_, _nargs == 2 || _nargs == 3, 2, "wrong number of arguments"); + // make sure we got a numeric limit + lua_Integer const _val{ luaL_optinteger(L_, 3, 0) }; + if (_val < 0) { + raise_luaL_argerror(L_, 3, "limit must be >= 0"); } - } else { // linda is cancelled - // do nothing and return nil,lanes.cancel_error - lua_pushnil(L_); - kCancelError.pushKey(L_); - _pushed.emplace(2); + // make sure the key is of a valid type + CheckKeyTypes(L_, 2, 2); + + KeeperCallResult _pushed; + if (_linda->cancelRequest == CancelRequest::None) { + Keeper* const _keeper{ _linda->whichKeeper() }; + _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2); + LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 1) && luaG_type(L_, -1) == LuaType::BOOLEAN); // no error, boolean value saying if we should wake blocked writer threads + if (lua_toboolean(L_, -1)) { + _linda->readHappened.notify_all(); // To be done from within the 'K' locking area + } + } else { // linda is cancelled + // do nothing and return nil,lanes.cancel_error + lua_pushnil(L_); + kCancelError.pushKey(L_); + _pushed.emplace(2); + } + // propagate pushed boolean if any + return _pushed.value(); } - // propagate pushed boolean if any - return _pushed.value(); }; return Linda::ProtectedCall(L_, _limit); } @@ -481,197 +500,63 @@ LUAG_FUNC(linda_limit) */ LUAG_FUNC(linda_receive) { - auto _receive = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - int _key_i{ 2 }; // index of first key, if timeout not there - - std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; - if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion - lua_Duration const _duration{ lua_tonumber(L_, 2) }; - if (_duration.count() >= 0.0) { - _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); - } else { - raise_luaL_argerror(L_, 2, "duration cannot be < 0"); - } - ++_key_i; - } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key - ++_key_i; - } - - keeper_api_t _selected_keeper_receive{ nullptr }; - int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 }; - // are we in batched mode? - if (kLindaBatched.equals(L_, _key_i)) { - // no need to pass linda.batched in the keeper state - ++_key_i; - // make sure the keys are of a valid type - check_key_types(L_, _key_i, _key_i); - // receive multiple values from a single slot - _selected_keeper_receive = KEEPER_API(receive_batched); - // we expect a user-defined amount of return value - _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1); - if (_expected_pushed_min < 1) { - raise_luaL_argerror(L_, _key_i + 1, "bad min count"); - } - _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min); - // don't forget to count the key in addition to the values - ++_expected_pushed_min; - ++_expected_pushed_max; - if (_expected_pushed_min > _expected_pushed_max) { - raise_luaL_argerror(L_, _key_i + 2, "batched min/max error"); - } - } else { - // make sure the keys are of a valid type - check_key_types(L_, _key_i, lua_gettop(L_)); - // receive a single value, checking multiple slots - _selected_keeper_receive = KEEPER_API(receive); - // we expect a single (value, key) pair of returned values - _expected_pushed_min = _expected_pushed_max = 2; - } - - Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; - Keeper* const _keeper{ _linda->whichKeeper() }; - KeeperState const _K{ _keeper ? _keeper->K : nullptr }; - if (_K == nullptr) - return 0; - - CancelRequest _cancel{ CancelRequest::None }; - KeeperCallResult _pushed{}; - STACK_CHECK_START_REL(_K, 0); - for (bool _try_again{ true };;) { - if (_lane != nullptr) { - _cancel = _lane->cancelRequest; - } - _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest; - // if user wants to cancel, or looped because of a timeout, the call returns without sending anything - if (!_try_again || _cancel != CancelRequest::None) { - _pushed.emplace(0); - break; - } - - // all arguments of receive() but the first are passed to the keeper's receive function - _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i); - if (!_pushed.has_value()) { - break; - } - if (_pushed.value() > 0) { - LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max); - _linda->readHappened.notify_all(); - break; - } - - if (std::chrono::steady_clock::now() >= _until) { - break; /* instant timeout */ - } - - // nothing received, wait until timeout or signalled that we should try again - { - Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings - if (_lane != nullptr) { - // change status of lane to "waiting" - _prev_status = _lane->status; // Running, most likely - LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case - _lane->status = Lane::Waiting; - LUA_ASSERT(L_, _lane->waiting_on == nullptr); - _lane->waiting_on = &_linda->writeHappened; - } - // not enough data to read: wakeup when data was sent, or when timeout is reached - std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; - std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; - _guard.release(); // we don't want to unlock the mutex on exit! - _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups - if (_lane != nullptr) { - _lane->waiting_on = nullptr; - _lane->status = _prev_status; + static constexpr lua_CFunction _receive{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + int _key_i{ 2 }; // index of first key, if timeout not there + + std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; + if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion + lua_Duration const _duration{ lua_tonumber(L_, 2) }; + if (_duration.count() >= 0.0) { + _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); + } else { + raise_luaL_argerror(L_, 2, "duration cannot be < 0"); } + ++_key_i; + } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key + ++_key_i; } - } - STACK_CHECK(_K, 0); - - if (!_pushed.has_value()) { - raise_luaL_error(L_, "tried to copy unsupported types"); - } - switch (_cancel) { - case CancelRequest::None: - { - int const _nbPushed{ _pushed.value() }; - if (_nbPushed == 0) { - // not enough data in the linda slot to fulfill the request, return nil, "timeout" - lua_pushnil(L_); - luaG_pushstring(L_, "timeout"); - return 2; + keeper_api_t _selected_keeper_receive{ nullptr }; + int _expected_pushed_min{ 0 }, _expected_pushed_max{ 0 }; + // are we in batched mode? + if (kLindaBatched.equals(L_, _key_i)) { + // no need to pass linda.batched in the keeper state + ++_key_i; + // make sure the keys are of a valid type + CheckKeyTypes(L_, _key_i, _key_i); + // receive multiple values from a single slot + _selected_keeper_receive = KEEPER_API(receive_batched); + // we expect a user-defined amount of return value + _expected_pushed_min = (int) luaL_checkinteger(L_, _key_i + 1); + if (_expected_pushed_min < 1) { + raise_luaL_argerror(L_, _key_i + 1, "bad min count"); + } + _expected_pushed_max = (int) luaL_optinteger(L_, _key_i + 2, _expected_pushed_min); + // don't forget to count the key in addition to the values + ++_expected_pushed_min; + ++_expected_pushed_max; + if (_expected_pushed_min > _expected_pushed_max) { + raise_luaL_argerror(L_, _key_i + 2, "batched min/max error"); } - return _nbPushed; - } - - case CancelRequest::Soft: - // if user wants to soft-cancel, the call returns nil, kCancelError - lua_pushnil(L_); - kCancelError.pushKey(L_); - return 2; - - case CancelRequest::Hard: - // raise an error interrupting execution only in case of hard cancel - raise_cancel_error(L_); // raises an error and doesn't return - - default: - raise_luaL_error(L_, "internal error: unknown cancel request"); - } - }; - return Linda::ProtectedCall(L_, _receive); -} - -// ################################################################################################# - -/* - * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...) - * - * Send one or more values to a Linda. If there is a limit, all values must fit. - * - * Returns: 'true' if the value was queued - * 'false' for timeout (only happens when the queue size is limited) - * nil, kCancelError if cancelled - */ -LUAG_FUNC(linda_send) -{ - auto _send = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - int _key_i{ 2 }; // index of first key, if timeout not there - - std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; - if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion - lua_Duration const _duration{ lua_tonumber(L_, 2) }; - if (_duration.count() >= 0.0) { - _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); } else { - raise_luaL_argerror(L_, 2, "duration cannot be < 0"); + // make sure the keys are of a valid type + CheckKeyTypes(L_, _key_i, lua_gettop(L_)); + // receive a single value, checking multiple slots + _selected_keeper_receive = KEEPER_API(receive); + // we expect a single (value, key) pair of returned values + _expected_pushed_min = _expected_pushed_max = 2; } - ++_key_i; - } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key - ++_key_i; - } - - // make sure the key is of a valid type - check_key_types(L_, _key_i, _key_i); - - STACK_GROW(L_, 1); - // make sure there is something to send - if (lua_gettop(L_) == _key_i) { - raise_luaL_error(L_, "no data to send"); - } - - bool _ret{ false }; - CancelRequest _cancel{ CancelRequest::None }; - KeeperCallResult _pushed; - { Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; Keeper* const _keeper{ _linda->whichKeeper() }; KeeperState const _K{ _keeper ? _keeper->K : nullptr }; if (_K == nullptr) return 0; + CancelRequest _cancel{ CancelRequest::None }; + KeeperCallResult _pushed{}; STACK_CHECK_START_REL(_K, 0); for (bool _try_again{ true };;) { if (_lane != nullptr) { @@ -684,28 +569,22 @@ LUAG_FUNC(linda_send) break; } - STACK_CHECK(_K, 0); - _pushed = keeper_call(_K, KEEPER_API(send), L_, _linda, _key_i); + // all arguments of receive() but the first are passed to the keeper's receive function + _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i); if (!_pushed.has_value()) { break; } - LUA_ASSERT(L_, _pushed.value() == 1); - - _ret = lua_toboolean(L_, -1) ? true : false; - lua_pop(L_, 1); - - if (_ret) { - // Wake up ALL waiting threads - _linda->writeHappened.notify_all(); + if (_pushed.value() > 0) { + LUA_ASSERT(L_, _pushed.value() >= _expected_pushed_min && _pushed.value() <= _expected_pushed_max); + _linda->readHappened.notify_all(); break; } - // instant timout to bypass the wait syscall if (std::chrono::steady_clock::now() >= _until) { - break; /* no wait; instant timeout */ + break; /* instant timeout */ } - // storage limit hit, wait until timeout or signalled that we should try again + // nothing received, wait until timeout or signalled that we should try again { Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings if (_lane != nullptr) { @@ -714,13 +593,13 @@ LUAG_FUNC(linda_send) LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case _lane->status = Lane::Waiting; LUA_ASSERT(L_, _lane->waiting_on == nullptr); - _lane->waiting_on = &_linda->readHappened; + _lane->waiting_on = &_linda->writeHappened; } - // could not send because no room: wait until some data was read before trying again, or until timeout is reached + // not enough data to read: wakeup when data was sent, or when timeout is reached std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; - std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; + std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; _guard.release(); // we don't want to unlock the mutex on exit! - _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups + _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups if (_lane != nullptr) { _lane->waiting_on = nullptr; _lane->status = _prev_status; @@ -728,32 +607,176 @@ LUAG_FUNC(linda_send) } } STACK_CHECK(_K, 0); - } - if (!_pushed.has_value()) { - raise_luaL_error(L_, "tried to copy unsupported types"); + if (!_pushed.has_value()) { + raise_luaL_error(L_, "tried to copy unsupported types"); + } + + switch (_cancel) { + case CancelRequest::None: + { + int const _nbPushed{ _pushed.value() }; + if (_nbPushed == 0) { + // not enough data in the linda slot to fulfill the request, return nil, "timeout" + lua_pushnil(L_); + luaG_pushstring(L_, "timeout"); + return 2; + } + return _nbPushed; + } + + case CancelRequest::Soft: + // if user wants to soft-cancel, the call returns nil, kCancelError + lua_pushnil(L_); + kCancelError.pushKey(L_); + return 2; + + case CancelRequest::Hard: + // raise an error interrupting execution only in case of hard cancel + raise_cancel_error(L_); // raises an error and doesn't return + + default: + raise_luaL_error(L_, "internal error: unknown cancel request"); + } } + }; + return Linda::ProtectedCall(L_, _receive); +} + +// ################################################################################################# + +/* + * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...) + * + * Send one or more values to a Linda. If there is a limit, all values must fit. + * + * Returns: 'true' if the value was queued + * 'false' for timeout (only happens when the queue size is limited) + * nil, kCancelError if cancelled + */ +LUAG_FUNC(linda_send) +{ + static constexpr lua_CFunction _send{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + int _key_i{ 2 }; // index of first key, if timeout not there + + std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; + if (luaG_type(L_, 2) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion + lua_Duration const _duration{ lua_tonumber(L_, 2) }; + if (_duration.count() >= 0.0) { + _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); + } else { + raise_luaL_argerror(L_, 2, "duration cannot be < 0"); + } + ++_key_i; + } else if (lua_isnil(L_, 2)) { // alternate explicit "infinite timeout" by passing nil before the key + ++_key_i; + } - switch (_cancel) { - case CancelRequest::Soft: - // if user wants to soft-cancel, the call returns nil, kCancelError - lua_pushnil(L_); - kCancelError.pushKey(L_); - return 2; + // make sure the key is of a valid type + CheckKeyTypes(L_, _key_i, _key_i); - case CancelRequest::Hard: - // raise an error interrupting execution only in case of hard cancel - raise_cancel_error(L_); // raises an error and doesn't return + STACK_GROW(L_, 1); - default: - if (_ret) { - lua_pushboolean(L_, _ret); // true (success) - return 1; - } else { - // not enough room in the Linda slot to fulfill the request, return nil, "timeout" + // make sure there is something to send + if (lua_gettop(L_) == _key_i) { + raise_luaL_error(L_, "no data to send"); + } + + bool _ret{ false }; + CancelRequest _cancel{ CancelRequest::None }; + KeeperCallResult _pushed; + { + Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; + Keeper* const _keeper{ _linda->whichKeeper() }; + KeeperState const _K{ _keeper ? _keeper->K : nullptr }; + if (_K == nullptr) + return 0; + + STACK_CHECK_START_REL(_K, 0); + for (bool _try_again{ true };;) { + if (_lane != nullptr) { + _cancel = _lane->cancelRequest; + } + _cancel = (_cancel != CancelRequest::None) ? _cancel : _linda->cancelRequest; + // if user wants to cancel, or looped because of a timeout, the call returns without sending anything + if (!_try_again || _cancel != CancelRequest::None) { + _pushed.emplace(0); + break; + } + + STACK_CHECK(_K, 0); + _pushed = keeper_call(_K, KEEPER_API(send), L_, _linda, _key_i); + if (!_pushed.has_value()) { + break; + } + LUA_ASSERT(L_, _pushed.value() == 1); + + _ret = lua_toboolean(L_, -1) ? true : false; + lua_pop(L_, 1); + + if (_ret) { + // Wake up ALL waiting threads + _linda->writeHappened.notify_all(); + break; + } + + // instant timout to bypass the wait syscall + if (std::chrono::steady_clock::now() >= _until) { + break; /* no wait; instant timeout */ + } + + // storage limit hit, wait until timeout or signalled that we should try again + { + Lane::Status _prev_status{ Lane::Error }; // prevent 'might be used uninitialized' warnings + if (_lane != nullptr) { + // change status of lane to "waiting" + _prev_status = _lane->status; // Running, most likely + LUA_ASSERT(L_, _prev_status == Lane::Running); // but check, just in case + _lane->status = Lane::Waiting; + LUA_ASSERT(L_, _lane->waiting_on == nullptr); + _lane->waiting_on = &_linda->readHappened; + } + // could not send because no room: wait until some data was read before trying again, or until timeout is reached + std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; + std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; + _guard.release(); // we don't want to unlock the mutex on exit! + _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups + if (_lane != nullptr) { + _lane->waiting_on = nullptr; + _lane->status = _prev_status; + } + } + } + STACK_CHECK(_K, 0); + } + + if (!_pushed.has_value()) { + raise_luaL_error(L_, "tried to copy unsupported types"); + } + + switch (_cancel) { + case CancelRequest::Soft: + // if user wants to soft-cancel, the call returns nil, kCancelError lua_pushnil(L_); - luaG_pushstring(L_, "timeout"); + kCancelError.pushKey(L_); return 2; + + case CancelRequest::Hard: + // raise an error interrupting execution only in case of hard cancel + raise_cancel_error(L_); // raises an error and doesn't return + + default: + if (_ret) { + lua_pushboolean(L_, _ret); // true (success) + return 1; + } else { + // not enough room in the Linda slot to fulfill the request, return nil, "timeout" + lua_pushnil(L_); + luaG_pushstring(L_, "timeout"); + return 2; + } } } }; @@ -771,39 +794,41 @@ LUAG_FUNC(linda_send) */ LUAG_FUNC(linda_set) { - auto set = [](lua_State* L_) { - Linda* const _linda{ ToLinda<false>(L_, 1) }; - bool const _has_data{ lua_gettop(L_) > 2 }; - // make sure the key is of a valid type (throws an error if not the case) - check_key_types(L_, 2, 2); - - KeeperCallResult _pushed; - if (_linda->cancelRequest == CancelRequest::None) { - Keeper* const _keeper{ _linda->whichKeeper() }; - _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, 2); - if (_pushed.has_value()) { // no error? - LUA_ASSERT(L_, _pushed.value() == 1 && luaG_type(L_, -1) == LuaType::BOOLEAN); - - if (_has_data) { - // we put some data in the slot, tell readers that they should wake - _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area - } - if (lua_toboolean(L_, -1)) { - // the key was full, but it is no longer the case, tell writers they should wake - _linda->readHappened.notify_all(); // To be done from within the 'K' locking area + static constexpr lua_CFunction _set{ + +[](lua_State* const L_) { + Linda* const _linda{ ToLinda<false>(L_, 1) }; + bool const _has_data{ lua_gettop(L_) > 2 }; + // make sure the key is of a valid type (throws an error if not the case) + CheckKeyTypes(L_, 2, 2); + + KeeperCallResult _pushed; + if (_linda->cancelRequest == CancelRequest::None) { + Keeper* const _keeper{ _linda->whichKeeper() }; + _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, 2); + if (_pushed.has_value()) { // no error? + LUA_ASSERT(L_, _pushed.value() == 1 && luaG_type(L_, -1) == LuaType::BOOLEAN); + + if (_has_data) { + // we put some data in the slot, tell readers that they should wake + _linda->writeHappened.notify_all(); // To be done from within the 'K' locking area + } + if (lua_toboolean(L_, -1)) { + // the key was full, but it is no longer the case, tell writers they should wake + _linda->readHappened.notify_all(); // To be done from within the 'K' locking area + } } + } else { // linda is cancelled + // do nothing and return nil,lanes.cancel_error + lua_pushnil(L_); + kCancelError.pushKey(L_); + _pushed.emplace(2); } - } else { // linda is cancelled - // do nothing and return nil,lanes.cancel_error - lua_pushnil(L_); - kCancelError.pushKey(L_); - _pushed.emplace(2); - } - // must trigger any error after keeper state has been released - return OptionalValue(_pushed, L_, "tried to copy unsupported types"); + // must trigger any error after keeper state has been released + return OptionalValue(_pushed, L_, "tried to copy unsupported types"); + } }; - return Linda::ProtectedCall(L_, set); + return Linda::ProtectedCall(L_, _set); } // ################################################################################################# diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp index 9ae2611..d5ebc9b 100644 --- a/src/lindafactory.cpp +++ b/src/lindafactory.cpp @@ -35,8 +35,7 @@ THE SOFTWARE. #include "linda.h" -// must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) -#define kLindaMetatableName "Linda" +static constexpr std::string_view kLindaMetatableName{ "Linda" }; // ################################################################################################# diff --git a/src/nameof.cpp b/src/nameof.cpp index a33c2e5..3c82603 100644 --- a/src/nameof.cpp +++ b/src/nameof.cpp @@ -206,4 +206,3 @@ LUAG_FUNC(nameof) lua_replace(L_, -3); // L_: "type" "result" return 2; } - diff --git a/src/state.cpp b/src/state.cpp index 267554e..3f6b3d7 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -50,7 +50,7 @@ namespace { // ############################################################################################# // ############################################################################################# - [[nodiscard]] static int require_lanes_core(lua_State* L_) + [[nodiscard]] static int require_lanes_core(lua_State* const L_) { // leaves a copy of 'lanes.core' module table on the stack luaL_requiref(L_, kLanesCoreLibName, luaopen_lanes_core, 0); @@ -95,7 +95,7 @@ namespace { // ############################################################################################# - static void Open1Lib(lua_State* L_, std::string_view const& name_) + static void Open1Lib(lua_State* const L_, std::string_view const& name_) { for (luaL_Reg const& _entry : local::sLibs) { if (name_ == _entry.name) { @@ -123,7 +123,7 @@ namespace { // ############################################################################################# // just like lua_xmove, args are (from, to) - static void CopyOneTimeSettings(Universe* U_, SourceState L1_, DestState L2_) + static void CopyOneTimeSettings(Universe* const U_, SourceState const L1_, DestState const L2_) { DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ U_ }); @@ -157,7 +157,7 @@ namespace state { // ############################################################################################# // ############################################################################################# - void CallOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_) + void CallOnStateCreate(Universe* const U_, lua_State* const L_, lua_State* const from_, LookupMode const mode_) { if (U_->onStateCreateFunc == nullptr) { return; @@ -195,7 +195,7 @@ namespace state { // ############################################################################################# - lua_State* CreateState([[maybe_unused]] Universe* U_, lua_State* from_) + lua_State* CreateState([[maybe_unused]] Universe* const U_, lua_State* const from_) { lua_State* const _L { std::invoke( @@ -228,7 +228,7 @@ namespace state { // ############################################################################################# - void InitializeOnStateCreate(Universe* U_, lua_State* L_) + void InitializeOnStateCreate(Universe* const U_, lua_State* const L_) { STACK_CHECK_START_REL(L_, 1); // L_: settings if (luaG_getfield(L_, -1, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil @@ -266,7 +266,7 @@ namespace state { * Base ("unpack", "print" etc.) is always added, unless 'libs' is nullptr. * */ - lua_State* NewLaneState(Universe* U_, SourceState from_, std::optional<std::string_view> const& libs_) + lua_State* NewLaneState(Universe* const U_, SourceState const from_, std::optional<std::string_view> const& libs_) { DestState const _L{ CreateState(U_, from_) }; diff --git a/src/tools.cpp b/src/tools.cpp index 9fc1e35..14786b2 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -65,15 +65,15 @@ static constexpr int kWriterReturnCode{ 666 }; * +-----------------+-------------------+------------+----------+ */ -[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i) +FuncSubType luaG_getfuncsubtype(lua_State* const L_, int const i_) { - if (lua_tocfunction(L_, _i)) { // nullptr for LuaJIT-fast && bytecode functions + if (lua_tocfunction(L_, i_)) { // nullptr for LuaJIT-fast && bytecode functions return FuncSubType::Native; } { int _mustpush{ 0 }; - if (luaG_absindex(L_, _i) != lua_gettop(L_)) { - lua_pushvalue(L_, _i); + if (luaG_absindex(L_, i_) != lua_gettop(L_)) { + lua_pushvalue(L_, i_); _mustpush = 1; } // the provided writer fails with code kWriterReturnCode @@ -93,11 +93,11 @@ static constexpr int kWriterReturnCode{ 666 }; namespace tools { // inspired from tconcat() in ltablib.c - [[nodiscard]] std::string_view PushFQN(lua_State* L_, int t_, int last_) + std::string_view PushFQN(lua_State* const L_, int const t_, int const last_) { - luaL_Buffer _b; STACK_CHECK_START_REL(L_, 0); // Lua 5.4 pushes &b as light userdata on the stack. be aware of it... + luaL_Buffer _b; luaL_buffinit(L_, &_b); // L_: ... {} ... &b? int _i{ 1 }; for (; _i < last_; ++_i) { diff --git a/src/tools.h b/src/tools.h index e240fdb..5127ea0 100644 --- a/src/tools.h +++ b/src/tools.h @@ -18,7 +18,7 @@ enum class FuncSubType FastJIT }; -[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i); +[[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int i_); // ################################################################################################# -- cgit v1.2.3-55-g6feb