diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/keeper.cpp | 8 | ||||
| -rw-r--r-- | src/keeper.h | 2 | ||||
| -rw-r--r-- | src/linda.cpp | 82 | ||||
| -rw-r--r-- | src/lindafactory.cpp | 13 |
4 files changed, 53 insertions, 52 deletions
diff --git a/src/keeper.cpp b/src/keeper.cpp index 3e806f9..b8f2bd0 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp | |||
| @@ -276,7 +276,7 @@ static void PushKeysDB(KeeperState const K_, int const idx_) | |||
| 276 | int keeper_push_linda_storage(Linda& linda_, DestState L_) | 276 | int keeper_push_linda_storage(Linda& linda_, DestState L_) |
| 277 | { | 277 | { |
| 278 | Keeper* const _keeper{ linda_.whichKeeper() }; | 278 | Keeper* const _keeper{ linda_.whichKeeper() }; |
| 279 | KeeperState const _K{ _keeper ? _keeper->L : nullptr }; | 279 | KeeperState const _K{ _keeper ? _keeper->K : nullptr }; |
| 280 | if (_K == nullptr) { | 280 | if (_K == nullptr) { |
| 281 | return 0; | 281 | return 0; |
| 282 | } | 282 | } |
| @@ -710,7 +710,7 @@ void Keeper::operator delete[](void* p_, Universe* U_) | |||
| 710 | // ################################################################################################# | 710 | // ################################################################################################# |
| 711 | // ################################################################################################# | 711 | // ################################################################################################# |
| 712 | 712 | ||
| 713 | void Keepers::DeleteKV::operator()(Keeper* k_) const | 713 | void Keepers::DeleteKV::operator()(Keeper* const k_) const |
| 714 | { | 714 | { |
| 715 | for (Keeper& _k : std::views::counted(k_, count)) { | 715 | for (Keeper& _k : std::views::counted(k_, count)) { |
| 716 | _k.~Keeper(); | 716 | _k.~Keeper(); |
| @@ -733,7 +733,7 @@ void Keepers::close() | |||
| 733 | } | 733 | } |
| 734 | 734 | ||
| 735 | auto _closeOneKeeper = [](Keeper& keeper_) { | 735 | auto _closeOneKeeper = [](Keeper& keeper_) { |
| 736 | lua_State* const _K{ std::exchange(keeper_.L, KeeperState{ nullptr }) }; | 736 | lua_State* const _K{ std::exchange(keeper_.K, KeeperState{ nullptr }) }; |
| 737 | if (_K) { | 737 | if (_K) { |
| 738 | lua_close(_K); | 738 | lua_close(_K); |
| 739 | } | 739 | } |
| @@ -824,7 +824,7 @@ void Keepers::initialize(Universe& U_, lua_State* L_, int const nbKeepers_, int | |||
| 824 | raise_luaL_error(L, "out of memory while creating keeper states"); | 824 | raise_luaL_error(L, "out of memory while creating keeper states"); |
| 825 | } | 825 | } |
| 826 | 826 | ||
| 827 | keeper_.L = _K; | 827 | keeper_.K = _K; |
| 828 | 828 | ||
| 829 | // Give a name to the state | 829 | // Give a name to the state |
| 830 | std::ignore = luaG_pushstringview(_K, "Keeper #%d", i_ + 1); // L_: settings _K: "Keeper #n" | 830 | std::ignore = luaG_pushstringview(_K, "Keeper #%d", i_ + 1); // L_: settings _K: "Keeper #n" |
diff --git a/src/keeper.h b/src/keeper.h index 1425a3b..5cc7422 100644 --- a/src/keeper.h +++ b/src/keeper.h | |||
| @@ -28,7 +28,7 @@ using KeeperState = Unique<lua_State*>; | |||
| 28 | struct Keeper | 28 | struct Keeper |
| 29 | { | 29 | { |
| 30 | std::mutex mutex; | 30 | std::mutex mutex; |
| 31 | KeeperState L{ nullptr }; | 31 | KeeperState K{ nullptr }; |
| 32 | 32 | ||
| 33 | [[nodiscard]] static void* operator new[](size_t size_, Universe* U_) noexcept; | 33 | [[nodiscard]] static void* operator new[](size_t size_, Universe* U_) noexcept; |
| 34 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | 34 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception |
diff --git a/src/linda.cpp b/src/linda.cpp index 3449f89..a7d7ee9 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
| @@ -136,11 +136,11 @@ Linda::~Linda() | |||
| 136 | Keeper* Linda::acquireKeeper() const | 136 | Keeper* Linda::acquireKeeper() const |
| 137 | { | 137 | { |
| 138 | // can be nullptr if this happens during main state shutdown (lanes is being GC'ed -> no keepers) | 138 | // can be nullptr if this happens during main state shutdown (lanes is being GC'ed -> no keepers) |
| 139 | Keeper* const _K{ whichKeeper() }; | 139 | Keeper* const _keeper{ whichKeeper() }; |
| 140 | if (_K) { | 140 | if (_keeper) { |
| 141 | _K->mutex.lock(); | 141 | _keeper->mutex.lock(); |
| 142 | } | 142 | } |
| 143 | return _K; | 143 | return _keeper; |
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | // ################################################################################################# | 146 | // ################################################################################################# |
| @@ -177,14 +177,14 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) | |||
| 177 | Linda* const _linda{ ToLinda<false>(L_, 1) }; | 177 | Linda* const _linda{ ToLinda<false>(L_, 1) }; |
| 178 | 178 | ||
| 179 | // acquire the keeper | 179 | // acquire the keeper |
| 180 | Keeper* const _K{ _linda->acquireKeeper() }; | 180 | Keeper* const _keeper{ _linda->acquireKeeper() }; |
| 181 | lua_State* const _KL{ _K ? _K->L : nullptr }; | 181 | KeeperState const _K{ _keeper ? _keeper->K : nullptr }; |
| 182 | if (_KL == nullptr) | 182 | if (_K == nullptr) |
| 183 | return 0; | 183 | return 0; |
| 184 | 184 | ||
| 185 | LUA_ASSERT_CODE(auto const _koip{ _linda->startKeeperOperation(L_) }); | 185 | LUA_ASSERT_CODE(auto const _koip{ _linda->startKeeperOperation(L_) }); |
| 186 | // if we didn't do anything wrong, the keeper stack should be clean | 186 | // if we didn't do anything wrong, the keeper stack should be clean |
| 187 | LUA_ASSERT(L_, lua_gettop(_KL) == 0); | 187 | LUA_ASSERT(L_, lua_gettop(_K) == 0); |
| 188 | 188 | ||
| 189 | // push the function to be called and move it before the arguments | 189 | // push the function to be called and move it before the arguments |
| 190 | lua_pushcfunction(L_, f_); | 190 | lua_pushcfunction(L_, f_); |
| @@ -192,10 +192,10 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) | |||
| 192 | // do a protected call | 192 | // do a protected call |
| 193 | LuaError const _rc{ lua_pcall(L_, lua_gettop(L_) - 1, LUA_MULTRET, 0) }; | 193 | LuaError const _rc{ lua_pcall(L_, lua_gettop(L_) - 1, LUA_MULTRET, 0) }; |
| 194 | // whatever happens, the keeper state stack must be empty when we are done | 194 | // whatever happens, the keeper state stack must be empty when we are done |
| 195 | lua_settop(_KL, 0); | 195 | lua_settop(_K, 0); |
| 196 | 196 | ||
| 197 | // release the keeper | 197 | // release the keeper |
| 198 | _linda->releaseKeeper(_K); | 198 | _linda->releaseKeeper(_keeper); |
| 199 | 199 | ||
| 200 | // if there was an error, forward it | 200 | // if there was an error, forward it |
| 201 | if (_rc != LuaError::OK) { | 201 | if (_rc != LuaError::OK) { |
| @@ -207,11 +207,11 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) | |||
| 207 | 207 | ||
| 208 | // ################################################################################################# | 208 | // ################################################################################################# |
| 209 | 209 | ||
| 210 | void Linda::releaseKeeper(Keeper* const K_) const | 210 | void Linda::releaseKeeper(Keeper* const keeper_) const |
| 211 | { | 211 | { |
| 212 | if (K_) { // can be nullptr if we tried to acquire during shutdown | 212 | if (keeper_) { // can be nullptr if we tried to acquire during shutdown |
| 213 | assert(K_ == whichKeeper()); | 213 | assert(keeper_ == whichKeeper()); |
| 214 | K_->mutex.unlock(); | 214 | keeper_->mutex.unlock(); |
| 215 | } | 215 | } |
| 216 | } | 216 | } |
| 217 | 217 | ||
| @@ -316,8 +316,8 @@ LUAG_FUNC(linda_count) | |||
| 316 | // make sure the keys are of a valid type | 316 | // make sure the keys are of a valid type |
| 317 | check_key_types(L_, 2, lua_gettop(L_)); | 317 | check_key_types(L_, 2, lua_gettop(L_)); |
| 318 | 318 | ||
| 319 | Keeper* const _K{ _linda->whichKeeper() }; | 319 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 320 | KeeperCallResult const _pushed{ keeper_call(_K->L, KEEPER_API(count), L_, _linda, 2) }; | 320 | KeeperCallResult const _pushed{ keeper_call(_keeper->K, KEEPER_API(count), L_, _linda, 2) }; |
| 321 | return OptionalValue(_pushed, L_, "tried to count an invalid key"); | 321 | return OptionalValue(_pushed, L_, "tried to count an invalid key"); |
| 322 | }; | 322 | }; |
| 323 | return Linda::ProtectedCall(L_, _count); | 323 | return Linda::ProtectedCall(L_, _count); |
| @@ -376,8 +376,8 @@ LUAG_FUNC(linda_get) | |||
| 376 | 376 | ||
| 377 | KeeperCallResult _pushed; | 377 | KeeperCallResult _pushed; |
| 378 | if (_linda->cancelRequest == CancelRequest::None) { | 378 | if (_linda->cancelRequest == CancelRequest::None) { |
| 379 | Keeper* const _K{ _linda->whichKeeper() }; | 379 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 380 | _pushed = keeper_call(_K->L, KEEPER_API(get), L_, _linda, 2); | 380 | _pushed = keeper_call(_keeper->K, KEEPER_API(get), L_, _linda, 2); |
| 381 | } else { // linda is cancelled | 381 | } else { // linda is cancelled |
| 382 | // do nothing and return lanes.cancel_error | 382 | // do nothing and return lanes.cancel_error |
| 383 | kCancelError.pushKey(L_); | 383 | kCancelError.pushKey(L_); |
| @@ -415,8 +415,8 @@ LUAG_FUNC(linda_limit) | |||
| 415 | 415 | ||
| 416 | KeeperCallResult _pushed; | 416 | KeeperCallResult _pushed; |
| 417 | if (_linda->cancelRequest == CancelRequest::None) { | 417 | if (_linda->cancelRequest == CancelRequest::None) { |
| 418 | Keeper* const _K{ _linda->whichKeeper() }; | 418 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 419 | _pushed = keeper_call(_K->L, KEEPER_API(limit), L_, _linda, 2); | 419 | _pushed = keeper_call(_keeper->K, KEEPER_API(limit), L_, _linda, 2); |
| 420 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 0 || _pushed.value() == 1)); // no error, optional boolean value saying if we should wake blocked writer threads | 420 | LUA_ASSERT(L_, _pushed.has_value() && (_pushed.value() == 0 || _pushed.value() == 1)); // no error, optional boolean value saying if we should wake blocked writer threads |
| 421 | if (_pushed.value() == 1) { | 421 | if (_pushed.value() == 1) { |
| 422 | LUA_ASSERT(L_, luaG_type(L_, -1) == LuaType::BOOLEAN && lua_toboolean(L_, -1) == 1); | 422 | LUA_ASSERT(L_, luaG_type(L_, -1) == LuaType::BOOLEAN && lua_toboolean(L_, -1) == 1); |
| @@ -497,14 +497,14 @@ LUAG_FUNC(linda_receive) | |||
| 497 | } | 497 | } |
| 498 | 498 | ||
| 499 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; | 499 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; |
| 500 | Keeper* const _K{ _linda->whichKeeper() }; | 500 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 501 | KeeperState const _KL{ _K ? _K->L : nullptr }; | 501 | KeeperState const _K{ _keeper ? _keeper->K : nullptr }; |
| 502 | if (_KL == nullptr) | 502 | if (_K == nullptr) |
| 503 | return 0; | 503 | return 0; |
| 504 | 504 | ||
| 505 | CancelRequest _cancel{ CancelRequest::None }; | 505 | CancelRequest _cancel{ CancelRequest::None }; |
| 506 | KeeperCallResult _pushed{}; | 506 | KeeperCallResult _pushed{}; |
| 507 | STACK_CHECK_START_REL(_KL, 0); | 507 | STACK_CHECK_START_REL(_K, 0); |
| 508 | for (bool _try_again{ true };;) { | 508 | for (bool _try_again{ true };;) { |
| 509 | if (_lane != nullptr) { | 509 | if (_lane != nullptr) { |
| 510 | _cancel = _lane->cancelRequest; | 510 | _cancel = _lane->cancelRequest; |
| @@ -517,7 +517,7 @@ LUAG_FUNC(linda_receive) | |||
| 517 | } | 517 | } |
| 518 | 518 | ||
| 519 | // all arguments of receive() but the first are passed to the keeper's receive function | 519 | // all arguments of receive() but the first are passed to the keeper's receive function |
| 520 | _pushed = keeper_call(_KL, _selected_keeper_receive, L_, _linda, _key_i); | 520 | _pushed = keeper_call(_K, _selected_keeper_receive, L_, _linda, _key_i); |
| 521 | if (!_pushed.has_value()) { | 521 | if (!_pushed.has_value()) { |
| 522 | break; | 522 | break; |
| 523 | } | 523 | } |
| @@ -543,9 +543,9 @@ LUAG_FUNC(linda_receive) | |||
| 543 | _lane->waiting_on = &_linda->writeHappened; | 543 | _lane->waiting_on = &_linda->writeHappened; |
| 544 | } | 544 | } |
| 545 | // not enough data to read: wakeup when data was sent, or when timeout is reached | 545 | // not enough data to read: wakeup when data was sent, or when timeout is reached |
| 546 | std::unique_lock<std::mutex> _keeper_lock{ _K->mutex, std::adopt_lock }; | 546 | std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; |
| 547 | std::cv_status const _status{ _linda->writeHappened.wait_until(_keeper_lock, _until) }; | 547 | std::cv_status const _status{ _linda->writeHappened.wait_until(_guard, _until) }; |
| 548 | _keeper_lock.release(); // we don't want to release the lock! | 548 | _guard.release(); // we don't want to unlock the mutex on exit! |
| 549 | _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups | 549 | _try_again = (_status == std::cv_status::no_timeout); // detect spurious wakeups |
| 550 | if (_lane != nullptr) { | 550 | if (_lane != nullptr) { |
| 551 | _lane->waiting_on = nullptr; | 551 | _lane->waiting_on = nullptr; |
| @@ -553,7 +553,7 @@ LUAG_FUNC(linda_receive) | |||
| 553 | } | 553 | } |
| 554 | } | 554 | } |
| 555 | } | 555 | } |
| 556 | STACK_CHECK(_KL, 0); | 556 | STACK_CHECK(_K, 0); |
| 557 | 557 | ||
| 558 | if (!_pushed.has_value()) { | 558 | if (!_pushed.has_value()) { |
| 559 | raise_luaL_error(L_, "tried to copy unsupported types"); | 559 | raise_luaL_error(L_, "tried to copy unsupported types"); |
| @@ -634,12 +634,12 @@ LUAG_FUNC(linda_send) | |||
| 634 | KeeperCallResult _pushed; | 634 | KeeperCallResult _pushed; |
| 635 | { | 635 | { |
| 636 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; | 636 | Lane* const _lane{ kLanePointerRegKey.readLightUserDataValue<Lane>(L_) }; |
| 637 | Keeper* const _K{ _linda->whichKeeper() }; | 637 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 638 | KeeperState const _KL{ _K ? _K->L : nullptr }; | 638 | KeeperState const _K{ _keeper ? _keeper->K : nullptr }; |
| 639 | if (_KL == nullptr) | 639 | if (_K == nullptr) |
| 640 | return 0; | 640 | return 0; |
| 641 | 641 | ||
| 642 | STACK_CHECK_START_REL(_KL, 0); | 642 | STACK_CHECK_START_REL(_K, 0); |
| 643 | for (bool _try_again{ true };;) { | 643 | for (bool _try_again{ true };;) { |
| 644 | if (_lane != nullptr) { | 644 | if (_lane != nullptr) { |
| 645 | _cancel = _lane->cancelRequest; | 645 | _cancel = _lane->cancelRequest; |
| @@ -651,8 +651,8 @@ LUAG_FUNC(linda_send) | |||
| 651 | break; | 651 | break; |
| 652 | } | 652 | } |
| 653 | 653 | ||
| 654 | STACK_CHECK(_KL, 0); | 654 | STACK_CHECK(_K, 0); |
| 655 | _pushed = keeper_call(_KL, KEEPER_API(send), L_, _linda, _key_i); | 655 | _pushed = keeper_call(_K, KEEPER_API(send), L_, _linda, _key_i); |
| 656 | if (!_pushed.has_value()) { | 656 | if (!_pushed.has_value()) { |
| 657 | break; | 657 | break; |
| 658 | } | 658 | } |
| @@ -684,9 +684,9 @@ LUAG_FUNC(linda_send) | |||
| 684 | _lane->waiting_on = &_linda->readHappened; | 684 | _lane->waiting_on = &_linda->readHappened; |
| 685 | } | 685 | } |
| 686 | // could not send because no room: wait until some data was read before trying again, or until timeout is reached | 686 | // could not send because no room: wait until some data was read before trying again, or until timeout is reached |
| 687 | std::unique_lock<std::mutex> _keeper_lock{ _K->mutex, std::adopt_lock }; | 687 | std::unique_lock<std::mutex> _guard{ _keeper->mutex, std::adopt_lock }; |
| 688 | std::cv_status const status{ _linda->readHappened.wait_until(_keeper_lock, _until) }; | 688 | std::cv_status const status{ _linda->readHappened.wait_until(_guard, _until) }; |
| 689 | _keeper_lock.release(); // we don't want to release the lock! | 689 | _guard.release(); // we don't want to unlock the mutex on exit! |
| 690 | _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups | 690 | _try_again = (status == std::cv_status::no_timeout); // detect spurious wakeups |
| 691 | if (_lane != nullptr) { | 691 | if (_lane != nullptr) { |
| 692 | _lane->waiting_on = nullptr; | 692 | _lane->waiting_on = nullptr; |
| @@ -694,7 +694,7 @@ LUAG_FUNC(linda_send) | |||
| 694 | } | 694 | } |
| 695 | } | 695 | } |
| 696 | } | 696 | } |
| 697 | STACK_CHECK(_KL, 0); | 697 | STACK_CHECK(_K, 0); |
| 698 | } | 698 | } |
| 699 | 699 | ||
| 700 | if (!_pushed.has_value()) { | 700 | if (!_pushed.has_value()) { |
| @@ -736,10 +736,10 @@ LUAG_FUNC(linda_set) | |||
| 736 | // make sure the key is of a valid type (throws an error if not the case) | 736 | // make sure the key is of a valid type (throws an error if not the case) |
| 737 | check_key_types(L_, 2, 2); | 737 | check_key_types(L_, 2, 2); |
| 738 | 738 | ||
| 739 | Keeper* const _K{ _linda->whichKeeper() }; | 739 | Keeper* const _keeper{ _linda->whichKeeper() }; |
| 740 | KeeperCallResult _pushed; | 740 | KeeperCallResult _pushed; |
| 741 | if (_linda->cancelRequest == CancelRequest::None) { | 741 | if (_linda->cancelRequest == CancelRequest::None) { |
| 742 | _pushed = keeper_call(_K->L, KEEPER_API(set), L_, _linda, 2); | 742 | _pushed = keeper_call(_keeper->K, KEEPER_API(set), L_, _linda, 2); |
| 743 | if (_pushed.has_value()) { // no error? | 743 | if (_pushed.has_value()) { // no error? |
| 744 | LUA_ASSERT(L_, _pushed.value() == 0 || _pushed.value() == 1); | 744 | LUA_ASSERT(L_, _pushed.value() == 0 || _pushed.value() == 1); |
| 745 | 745 | ||
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp index 6a2b000..9a75b4f 100644 --- a/src/lindafactory.cpp +++ b/src/lindafactory.cpp | |||
| @@ -70,19 +70,20 @@ void LindaFactory::deleteDeepObjectInternal(lua_State* L_, DeepPrelude* o_) cons | |||
| 70 | { | 70 | { |
| 71 | Linda* const _linda{ static_cast<Linda*>(o_) }; | 71 | Linda* const _linda{ static_cast<Linda*>(o_) }; |
| 72 | LUA_ASSERT(L_, _linda && !_linda->inKeeperOperation()); | 72 | LUA_ASSERT(L_, _linda && !_linda->inKeeperOperation()); |
| 73 | Keeper* const _myK{ _linda->whichKeeper() }; | 73 | Keeper* const _myKeeper{ _linda->whichKeeper() }; |
| 74 | // if collected after the universe, keepers are already destroyed, and there is nothing to clear | 74 | // if collected after the universe, keepers are already destroyed, and there is nothing to clear |
| 75 | if (_myK) { | 75 | if (_myKeeper) { |
| 76 | // if collected from my own keeper, we can't acquire/release it | 76 | // if collected from my own keeper, we can't acquire/release it |
| 77 | // because we are already inside a protected area, and trying to do so would deadlock! | 77 | // because we are already inside a protected area, and trying to do so would deadlock! |
| 78 | bool const _need_acquire_release{ _myK->L != L_ }; | 78 | bool const _need_acquire_release{ _myKeeper->K != L_ }; |
| 79 | // Clean associated structures in the keeper state. | 79 | // Clean associated structures in the keeper state. |
| 80 | Keeper* const _K{ _need_acquire_release ? _linda->acquireKeeper() : _myK }; | 80 | Keeper* const _keeper{ _need_acquire_release ? _linda->acquireKeeper() : _myKeeper }; |
| 81 | LUA_ASSERT(L_, _keeper == _myKeeper); // should always be the same | ||
| 81 | // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... | 82 | // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... |
| 82 | [[maybe_unused]] KeeperCallResult const result{ keeper_call(_K->L, KEEPER_API(destruct), L_, _linda, 0) }; | 83 | [[maybe_unused]] KeeperCallResult const result{ keeper_call(_keeper->K, KEEPER_API(destruct), L_, _linda, 0) }; |
| 83 | LUA_ASSERT(L_, result.has_value() && result.value() == 0); | 84 | LUA_ASSERT(L_, result.has_value() && result.value() == 0); |
| 84 | if (_need_acquire_release) { | 85 | if (_need_acquire_release) { |
| 85 | _linda->releaseKeeper(_K); | 86 | _linda->releaseKeeper(_keeper); |
| 86 | } | 87 | } |
| 87 | } | 88 | } |
| 88 | 89 | ||
