aboutsummaryrefslogtreecommitdiff
path: root/src/universe.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/universe.cpp')
-rw-r--r--src/universe.cpp107
1 files changed, 67 insertions, 40 deletions
diff --git a/src/universe.cpp b/src/universe.cpp
index 89ad02a..4db036b 100644
--- a/src/universe.cpp
+++ b/src/universe.cpp
@@ -51,6 +51,16 @@ static constexpr RegistryUniqueKey kUniverseFullRegKey{ 0x1C2D76870DD9DD9Full };
51 51
52// ################################################################################################# 52// #################################################################################################
53 53
54[[nodiscard]]
55void* ProtectedAllocator::Protected_lua_Alloc(void* const ud_, void* const ptr_, size_t const osize_, size_t const nsize_)
56{
57 ProtectedAllocator* const _allocator{ static_cast<ProtectedAllocator*>(ud_) };
58 std::lock_guard<std::mutex> _guard{ _allocator->mutex };
59 return _allocator->alloc(ptr_, osize_, nsize_);
60}
61
62// #################################################################################################
63
54Universe::Universe() 64Universe::Universe()
55{ 65{
56 //--- 66 //---
@@ -101,18 +111,18 @@ void Universe::callOnStateCreate(lua_State* const L_, lua_State* const from_, Lo
101 } 111 }
102 kConfigRegKey.pushValue(L_); // L_: config 112 kConfigRegKey.pushValue(L_); // L_: config
103 STACK_CHECK(L_, 1); 113 STACK_CHECK(L_, 1);
104 LuaType const _funcType{ luaG_getfield(L_, kIdxTop, kOnStateCreate) }; // L_: config on_state_create() 114 LuaType const _funcType{ luaW_getfield(L_, kIdxTop, kOnStateCreate) }; // L_: config on_state_create()
105 if (_funcType != LuaType::FUNCTION) { 115 if (_funcType != LuaType::FUNCTION) {
106 raise_luaL_error(L_, "INTERNAL ERROR: %s is a %s, not a function", kOnStateCreate.data(), luaG_typename(L_, _funcType).data()); 116 raise_luaL_error(L_, "INTERNAL ERROR: %s is a %s, not a function", kOnStateCreate.data(), luaW_typename(L_, _funcType).data());
107 } 117 }
108 lua_remove(L_, -2); // L_: on_state_create() 118 lua_remove(L_, -2); // L_: on_state_create()
109 } 119 }
110 STACK_CHECK(L_, 1); 120 STACK_CHECK(L_, 1);
111 // capture error and raise it in caller state 121 // capture error and raise it in caller state
112 std::string_view const _stateType{ mode_ == LookupMode::LaneBody ? "lane" : "keeper" }; 122 std::string_view const _stateType{ mode_ == LookupMode::LaneBody ? "lane" : "keeper" };
113 luaG_pushstring(L_, _stateType); // L_: on_state_create() "<type>" 123 luaW_pushstring(L_, _stateType); // L_: on_state_create() "<type>"
114 if (lua_pcall(L_, 1, 0, 0) != LUA_OK) { 124 if (lua_pcall(L_, 1, 0, 0) != LUA_OK) {
115 raise_luaL_error(from_, "%s failed in %s: \"%s\"", kOnStateCreate.data(), _stateType.data(), lua_isstring(L_, -1) ? luaG_tostring(L_, kIdxTop).data() : luaG_typename(L_, kIdxTop).data()); 125 raise_luaL_error(from_, "%s failed in %s: \"%s\"", kOnStateCreate.data(), _stateType.data(), lua_isstring(L_, -1) ? luaW_tostring(L_, kIdxTop).data() : luaW_typename(L_, kIdxTop).data());
116 } 126 }
117 STACK_CHECK(L_, 0); 127 STACK_CHECK(L_, 0);
118} 128}
@@ -127,14 +137,14 @@ Universe* Universe::Create(lua_State* const L_)
127 static constexpr StackIndex kIdxSettings{ 1 }; 137 static constexpr StackIndex kIdxSettings{ 1 };
128 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); 138 LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1));
129 STACK_CHECK_START_REL(L_, 0); // L_: settings 139 STACK_CHECK_START_REL(L_, 0); // L_: settings
130 std::ignore = luaG_getfield(L_, kIdxSettings, "nb_user_keepers"); // L_: settings nb_user_keepers 140 std::ignore = luaW_getfield(L_, kIdxSettings, "nb_user_keepers"); // L_: settings nb_user_keepers
131 int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1}; 141 int const _nbUserKeepers{ static_cast<int>(lua_tointeger(L_, -1)) + 1};
132 lua_pop(L_, 1); // L_: settings 142 lua_pop(L_, 1); // L_: settings
133 if (_nbUserKeepers < 1) { 143 if (_nbUserKeepers < 1) {
134 raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers); 144 raise_luaL_error(L_, "Bad number of additional keepers (%d)", _nbUserKeepers);
135 } 145 }
136 STACK_CHECK(L_, 0); 146 STACK_CHECK(L_, 0);
137 std::ignore = luaG_getfield(L_, kIdxSettings, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold 147 std::ignore = luaW_getfield(L_, kIdxSettings, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold
138 int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) }; 148 int const _keepers_gc_threshold{ static_cast<int>(lua_tointeger(L_, -1)) };
139 lua_pop(L_, 1); // L_: settings 149 lua_pop(L_, 1); // L_: settings
140 STACK_CHECK(L_, 0); 150 STACK_CHECK(L_, 0);
@@ -147,22 +157,30 @@ Universe* Universe::Create(lua_State* const L_)
147 157
148 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); 158 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
149 lua_createtable(L_, 0, 1); // L_: settings universe {mt} 159 lua_createtable(L_, 0, 1); // L_: settings universe {mt}
150 std::ignore = luaG_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout 160 std::ignore = luaW_getfield(L_, kIdxSettings, "shutdown_timeout"); // L_: settings universe {mt} shutdown_timeout
151 lua_pushcclosure(L_, UniverseGC, 1); // L_: settings universe {mt} UniverseGC 161 lua_pushcclosure(L_, UniverseGC, 1); // L_: settings universe {mt} UniverseGC
152 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt} 162 lua_setfield(L_, -2, "__gc"); // L_: settings universe {mt}
153 lua_setmetatable(L_, -2); // L_: settings universe 163 lua_setmetatable(L_, -2); // L_: settings universe
154 lua_pop(L_, 1); // L_: settings 164 lua_pop(L_, 1); // L_: settings
155 165
156 std::ignore = luaG_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions 166 std::ignore = luaW_getfield(L_, kIdxSettings, "linda_wake_period"); // L_: settings linda_wake_period
167 if (luaW_type(L_, kIdxTop) == LuaType::NUMBER) {
168 _U->lindaWakePeriod = lua_Duration{ lua_tonumber(L_, kIdxTop) };
169 } else {
170 LUA_ASSERT(L_, luaW_tostring(L_, kIdxTop) == "never");
171 }
172 lua_pop(L_, 1); // L_: settings
173
174 std::ignore = luaW_getfield(L_, kIdxSettings, "strip_functions"); // L_: settings strip_functions
157 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false; 175 _U->stripFunctions = lua_toboolean(L_, -1) ? true : false;
158 lua_pop(L_, 1); // L_: settings 176 lua_pop(L_, 1); // L_: settings
159 177
160 std::ignore = luaG_getfield(L_, kIdxSettings, "verbose_errors"); // L_: settings verbose_errors 178 std::ignore = luaW_getfield(L_, kIdxSettings, "verbose_errors"); // L_: settings verbose_errors
161 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false; 179 _U->verboseErrors = lua_toboolean(L_, -1) ? true : false;
162 lua_pop(L_, 1); // L_: settings 180 lua_pop(L_, 1); // L_: settings
163 181
164 // tracking 182 // tracking
165 std::ignore = luaG_getfield(L_, kIdxSettings, "track_lanes"); // L_: settings track_lanes 183 std::ignore = luaW_getfield(L_, kIdxSettings, "track_lanes"); // L_: settings track_lanes
166 if (lua_toboolean(L_, -1)) { 184 if (lua_toboolean(L_, -1)) {
167 _U->tracker.activate(); 185 _U->tracker.activate();
168 } 186 }
@@ -170,8 +188,8 @@ Universe* Universe::Create(lua_State* const L_)
170 188
171 // Linked chains handling 189 // Linked chains handling
172 _U->selfdestructFirst = SELFDESTRUCT_END; 190 _U->selfdestructFirst = SELFDESTRUCT_END;
173 _U->initializeAllocatorFunction(L_); 191 _U->initializeAllocatorFunction(L_); // this can raise an error
174 _U->initializeOnStateCreate(L_); 192 _U->initializeOnStateCreate(L_); // this can raise an error
175 _U->keepers.initialize(*_U, L_, static_cast<size_t>(_nbUserKeepers), _keepers_gc_threshold); 193 _U->keepers.initialize(*_U, L_, static_cast<size_t>(_nbUserKeepers), _keepers_gc_threshold);
176 STACK_CHECK(L_, 0); 194 STACK_CHECK(L_, 0);
177 195
@@ -199,7 +217,7 @@ static void* libc_lua_Alloc([[maybe_unused]] void* const ud_, [[maybe_unused]] v
199// ################################################################################################# 217// #################################################################################################
200 218
201[[nodiscard]] 219[[nodiscard]]
202static int luaG_provide_protected_allocator(lua_State* const L_) 220static int luaW_provide_protected_allocator(lua_State* const L_)
203{ 221{
204 Universe* const _U{ Universe::Get(L_) }; 222 Universe* const _U{ Universe::Get(L_) };
205 // push a new full userdata on the stack, giving access to the universe's protected allocator 223 // push a new full userdata on the stack, giving access to the universe's protected allocator
@@ -209,9 +227,9 @@ static int luaG_provide_protected_allocator(lua_State* const L_)
209 227
210// ################################################################################################# 228// #################################################################################################
211 229
230// already called under protection of selfdestructMutex
212void Universe::flagDanglingLanes() const 231void Universe::flagDanglingLanes() const
213{ 232{
214 std::lock_guard<std::mutex> _guard{ selfdestructMutex };
215 Lane* _lane{ selfdestructFirst }; 233 Lane* _lane{ selfdestructFirst };
216 while (_lane != SELFDESTRUCT_END) { 234 while (_lane != SELFDESTRUCT_END) {
217 _lane->flaggedAfterUniverseGC.store(true, std::memory_order_relaxed); 235 _lane->flaggedAfterUniverseGC.store(true, std::memory_order_relaxed);
@@ -227,17 +245,17 @@ void Universe::initializeAllocatorFunction(lua_State* const L_)
227 // start by just grabbing whatever allocator was provided to the master state 245 // start by just grabbing whatever allocator was provided to the master state
228 protectedAllocator.initFrom(L_); 246 protectedAllocator.initFrom(L_);
229 STACK_CHECK_START_REL(L_, 1); // L_: settings 247 STACK_CHECK_START_REL(L_, 1); // L_: settings
230 switch (luaG_getfield(L_, kIdxTop, "allocator")) { // L_: settings allocator|nil|"protected" 248 switch (luaW_getfield(L_, kIdxTop, "allocator")) { // L_: settings allocator|nil|"protected"
231 case LuaType::NIL: 249 case LuaType::NIL:
232 // nothing else to do 250 // nothing else to do
233 break; 251 break;
234 252
235 case LuaType::STRING: 253 case LuaType::STRING:
236 LUA_ASSERT(L_, luaG_tostring(L_, kIdxTop) == "protected"); 254 LUA_ASSERT(L_, luaW_tostring(L_, kIdxTop) == "protected");
237 // set the original allocator to call from inside protection by the mutex 255 // set the original allocator to call from inside protection by the mutex
238 protectedAllocator.installIn(L_); 256 protectedAllocator.installIn(L_);
239 // before a state is created, this function will be called to obtain the allocator 257 // before a state is created, this function will be called to obtain the allocator
240 provideAllocator = luaG_provide_protected_allocator; 258 provideAllocator = luaW_provide_protected_allocator;
241 break; 259 break;
242 260
243 case LuaType::FUNCTION: 261 case LuaType::FUNCTION:
@@ -258,14 +276,14 @@ void Universe::initializeAllocatorFunction(lua_State* const L_)
258 break; 276 break;
259 277
260 default: // should be filtered out in lanes.lua 278 default: // should be filtered out in lanes.lua
261 raise_luaL_error(L_, "Bad config.allocator type %s", luaG_typename(L_, kIdxTop).data()); 279 raise_luaL_error(L_, "Bad config.allocator type %s", luaW_typename(L_, kIdxTop).data());
262 } 280 }
263 lua_pop(L_, 1); // L_: settings 281 lua_pop(L_, 1); // L_: settings
264 STACK_CHECK(L_, 1); 282 STACK_CHECK(L_, 1);
265 283
266 std::ignore = luaG_getfield(L_, kIdxTop, "internal_allocator"); // L_: settings "libc"|"allocator" 284 std::ignore = luaW_getfield(L_, kIdxTop, "internal_allocator"); // L_: settings "libc"|"allocator"
267 LUA_ASSERT(L_, lua_isstring(L_, kIdxTop)); // should be the case due to lanes.lua parameter validation 285 LUA_ASSERT(L_, lua_isstring(L_, kIdxTop)); // should be the case due to lanes.lua parameter validation
268 std::string_view const _allocator{ luaG_tostring(L_, kIdxTop) }; 286 std::string_view const _allocator{ luaW_tostring(L_, kIdxTop) };
269 // use whatever the provider provides. This performs validation of what provideAllocator is giving 287 // use whatever the provider provides. This performs validation of what provideAllocator is giving
270 // we do this even if _allocator == "libc", to have the validation part 288 // we do this even if _allocator == "libc", to have the validation part
271 internalAllocator = resolveAndValidateAllocator(L_, "internal"); 289 internalAllocator = resolveAndValidateAllocator(L_, "internal");
@@ -287,7 +305,7 @@ int Universe::InitializeFinalizer(lua_State* const L_)
287 305
288 // make sure we are only called from the Master Lua State! 306 // make sure we are only called from the Master Lua State!
289 kUniverseFullRegKey.pushValue(L_); // L_: f U 307 kUniverseFullRegKey.pushValue(L_); // L_: f U
290 if (luaG_type(L_, kIdxTop) != LuaType::USERDATA) { 308 if (luaW_type(L_, kIdxTop) != LuaType::USERDATA) {
291 raise_luaL_error(L_, "lanes.%s called from inside a lane", kFinally); 309 raise_luaL_error(L_, "lanes.%s called from inside a lane", kFinally);
292 } 310 }
293 lua_pop(L_, 1); // L_: f 311 lua_pop(L_, 1); // L_: f
@@ -303,8 +321,8 @@ int Universe::InitializeFinalizer(lua_State* const L_)
303void Universe::initializeOnStateCreate(lua_State* const L_) 321void Universe::initializeOnStateCreate(lua_State* const L_)
304{ 322{
305 STACK_CHECK_START_REL(L_, 0); // L_: settings 323 STACK_CHECK_START_REL(L_, 0); // L_: settings
306 if (luaG_getfield(L_, kIdxTop, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil 324 if (luaW_getfield(L_, kIdxTop, kOnStateCreate) != LuaType::NIL) { // L_: settings on_state_create|nil
307 LUA_ASSERT(L_, luaG_type(L_, kIdxTop) == LuaType::FUNCTION); // ensured by lanes.lua parameter validation 325 LUA_ASSERT(L_, luaW_type(L_, kIdxTop) == LuaType::FUNCTION); // ensured by lanes.lua parameter validation
308 // store C function pointer in an internal variable 326 // store C function pointer in an internal variable
309 lua_CFunction const _func{ lua_tocfunction(L_, -1) }; // L_: settings on_state_create 327 lua_CFunction const _func{ lua_tocfunction(L_, -1) }; // L_: settings on_state_create
310 if (_func) { 328 if (_func) {
@@ -317,7 +335,7 @@ void Universe::initializeOnStateCreate(lua_State* const L_)
317 // remove this C function from the config table so that it doesn't cause problems 335 // remove this C function from the config table so that it doesn't cause problems
318 // when we transfer the config table in newly created Lua states 336 // when we transfer the config table in newly created Lua states
319 lua_pushnil(L_); // L_: settings on_state_create nil 337 lua_pushnil(L_); // L_: settings on_state_create nil
320 luaG_setfield(L_, StackIndex{ -3 }, kOnStateCreate); // L_: settings on_state_create 338 luaW_setfield(L_, StackIndex{ -3 }, kOnStateCreate); // L_: settings on_state_create
321 } else { 339 } else {
322 // the function is still in the config table. we indicate this with the uintptr_t alternative (actual value is irrelevant) 340 // the function is still in the config table. we indicate this with the uintptr_t alternative (actual value is irrelevant)
323 onStateCreateFunc.emplace<uintptr_t>(std::bit_cast<uintptr_t>(kOnStateCreate.data())); 341 onStateCreateFunc.emplace<uintptr_t>(std::bit_cast<uintptr_t>(kOnStateCreate.data()));
@@ -340,7 +358,7 @@ lanes::AllocatorDefinition Universe::resolveAndValidateAllocator(lua_State* cons
340 358
341 STACK_CHECK_START_REL(L_, 0); // here, we have a function we can call to obtain an allocator 359 STACK_CHECK_START_REL(L_, 0); // here, we have a function we can call to obtain an allocator
342 lua_pushcclosure(L_, provideAllocator, 0); // L_: provideAllocator() 360 lua_pushcclosure(L_, provideAllocator, 0); // L_: provideAllocator()
343 luaG_pushstring(L_, hint_); // L_: provideAllocator() "<hint>" 361 luaW_pushstring(L_, hint_); // L_: provideAllocator() "<hint>"
344 lua_call(L_, 1, 1); // L_: result 362 lua_call(L_, 1, 1); // L_: result
345 // make sure we have a valid AllocatorDefinition on the stack (an error is raised instead if it is not the case) 363 // make sure we have a valid AllocatorDefinition on the stack (an error is raised instead if it is not the case)
346 _ret = lanes::AllocatorDefinition::Validated(L_, kIdxTop); 364 _ret = lanes::AllocatorDefinition::Validated(L_, kIdxTop);
@@ -415,7 +433,7 @@ int Universe::UniverseGC(lua_State* const L_)
415{ 433{
416 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) }; 434 lua_Duration const _shutdown_timeout{ lua_tonumber(L_, lua_upvalueindex(1)) };
417 STACK_CHECK_START_ABS(L_, 1); 435 STACK_CHECK_START_ABS(L_, 1);
418 Universe* const _U{ luaG_tofulluserdata<Universe>(L_, StackIndex{ 1 }) }; // L_: U 436 Universe* const _U{ luaW_tofulluserdata<Universe>(L_, StackIndex{ 1 }) }; // L_: U
419 437
420 // attempt to terminate all lanes with increasingly stronger cancel methods 438 // attempt to terminate all lanes with increasingly stronger cancel methods
421 bool const _allLanesTerminated{ 439 bool const _allLanesTerminated{
@@ -429,7 +447,7 @@ int Universe::UniverseGC(lua_State* const L_)
429 if (!lua_isnil(L_, -1)) { 447 if (!lua_isnil(L_, -1)) {
430 lua_pushboolean(L_, _allLanesTerminated); // L_: U finalizer bool 448 lua_pushboolean(L_, _allLanesTerminated); // L_: U finalizer bool
431 // no protection. Lua rules for errors in finalizers apply normally: 449 // no protection. Lua rules for errors in finalizers apply normally:
432 // Lua 5.4: error is propagated in the warn system 450 // Lua 5.4+: error is propagated in the warn system
433 // older: error is swallowed 451 // older: error is swallowed
434 lua_call(L_, 1, 1); // L_: U msg? 452 lua_call(L_, 1, 1); // L_: U msg?
435 // phew, no error in finalizer, since we reached that point 453 // phew, no error in finalizer, since we reached that point
@@ -438,24 +456,31 @@ int Universe::UniverseGC(lua_State* const L_)
438 if (lua_isnil(L_, kIdxTop)) { 456 if (lua_isnil(L_, kIdxTop)) {
439 lua_pop(L_, 1); // L_: U 457 lua_pop(L_, 1); // L_: U
440 // no finalizer, or it returned no value: push some default message on the stack, in case it is necessary 458 // no finalizer, or it returned no value: push some default message on the stack, in case it is necessary
441 luaG_pushstring(L_, "uncooperative lanes detected at shutdown"); // L_: U "msg" 459 luaW_pushstring(L_, "uncooperative lanes detected at shutdown"); // L_: U "msg"
442 } 460 }
443 STACK_CHECK(L_, 2); 461 STACK_CHECK(L_, 2);
444 462
445 // now, all remaining lanes are flagged. if they crash because we remove keepers and the Universe from under them, it is their fault 463 {
446 bool const _detectedUncooperativeLanes{ _U->selfdestructFirst != SELFDESTRUCT_END }; 464 std::lock_guard<std::mutex> _guard{ _U->selfdestructMutex };
447 if (_detectedUncooperativeLanes) { 465 // now, all remaining lanes are flagged. if they crash because we remove keepers and the Universe from under them, it is their fault
448 _U->flagDanglingLanes(); 466 bool const _detectedUncooperativeLanes{ _U->selfdestructFirst != SELFDESTRUCT_END };
449 if (luaG_tostring(L_, kIdxTop) == "freeze") { 467 if (_detectedUncooperativeLanes) {
450 std::this_thread::sleep_until(std::chrono::time_point<std::chrono::steady_clock>::max()); 468 _U->flagDanglingLanes();
469 if (luaW_tostring(L_, kIdxTop) == "freeze") {
470 std::this_thread::sleep_until(std::chrono::time_point<std::chrono::steady_clock>::max());
471 } else {
472 // take the value returned by the finalizer (or our default message) and throw it as an error
473 // since we are inside Lua's GCTM, it will be propagated through the warning system (Lua 5.4) or swallowed silently
474 // IMPORTANT: lua_error() is used here instead of the wrapper raise_lua_error() to circumvent what looks like a MSVC compiler bug
475 // that manifests as a crash inside ntdll!longjmp() function, in optimized builds only
476 lua_error(L_);
477 }
451 } else { 478 } else {
452 // take the value returned by the finalizer (or our default message) and throw it as an error 479 // we didn't use the error message, let's keep a clean stack
453 // since we are inside Lua's GCTM, it will be propagated through the warning system (Lua 5.4) or swallowed silently 480 lua_pop(L_, 1); // L_: U
454 // IMPORTANT: lua_error() is used here instead of the wrapper raise_lua_error() to circumvent what looks like a MSVC compiler bug
455 // that manifests as a crash inside ntdll!longjmp() function, in optimized builds only
456 lua_error(L_);
457 } 481 }
458 } 482 }
483 STACK_CHECK(L_, 1);
459 484
460 // --------------------------------------------------------- 485 // ---------------------------------------------------------
461 // we don't reach that point if some lanes are still running 486 // we don't reach that point if some lanes are still running
@@ -464,7 +489,9 @@ int Universe::UniverseGC(lua_State* const L_)
464 // no need to mutex-protect this as all lanes in the universe are gone at that point 489 // no need to mutex-protect this as all lanes in the universe are gone at that point
465 Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK); 490 Linda::DeleteTimerLinda(L_, std::exchange(_U->timerLinda, nullptr), PK);
466 491
467 _U->keepers.close(); 492 if (!_U->keepers.close()) {
493 raise_luaL_error(L_, "INTERNAL ERROR: Keepers closed more than once");
494 }
468 495
469 // remove the protected allocator, if any 496 // remove the protected allocator, if any
470 _U->protectedAllocator.removeFrom(L_); 497 _U->protectedAllocator.removeFrom(L_);