• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jordanvrtanoski / luacpp / df1dc034-d7b3-4443-a96c-5c1d6247f4da

18 Feb 2026 11:53PM UTC coverage: 99.518% (-0.5%) from 100.0%
df1dc034-d7b3-4443-a96c-5c1d6247f4da

Pull #34

circleci

jordanvrtanoski
[IMP] Add Lua state pooling with colored pools

Implement opt-in state pooling system for high-performance scenarios:
- StatePool class for managing reusable lua_State instances
- PoolManager with predefined pool colors (default, sandboxed, minimal, io)
- Selective library loading per pool configuration
- PooledState RAII wrapper for exception-safe state management
- Thread-safe pool mode for multi-threaded applications
- Pool warmup/drain/statistics methods
- PoolExhaustedException for handling pool exhaustion

Add LuaContext integration:
- RunPooled(), RunWithEnvironmentPooled() methods
- AcquirePooledState(), ReleasePooledState() methods
- AcquirePooledStateRAII() for automatic state return
- createPool(), hasPool(), getPool() management methods

Add comprehensive documentation and examples:
- docs/2-state-pooling.md: Complete pooling documentation
- docs/1-introduction.md: Added pooling overview section
- example_StatePool.cpp: Basic pooling usage
- example_StatePoolAdvanced.cpp: Advanced patterns with threads

All 126 tests passing including 56 new pooling tests.

Signed-off-by: Jordan Vrtanoski <jordan.vrtanoski@gmail.com>
Pull Request #34: [IMP] Add Lua state pooling with colored pools

975 of 987 new or added lines in 10 files covered. (98.78%)

1 existing line in 1 file now uncovered.

2682 of 2695 relevant lines covered (99.52%)

49.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.34
/Source/UnitTest/TestStatePool.cpp
1
/*
2
   MIT License
3

4
   Copyright (c) 2021 Jordan Vrtanoski
5

6
   Permission is hereby granted, free of charge, to any person obtaining a copy
7
   of this software and associated documentation files (the "Software"), to deal
8
   in the Software without restriction, including without limitation the rights
9
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
   copies of the Software, and to permit persons to whom the Software is
11
   furnished to do so, subject to the following conditions:
12

13
   The above copyright notice and this permission notice shall be included in all
14
   copies or substantial portions of the Software.
15

16
   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
   SOFTWARE.
23
   */
24

25
#include "../LuaCpp.hpp"
26
#include "gtest/gtest.h"
27

28
using namespace LuaCpp;
29
using namespace LuaCpp::Engine;
30

31
extern "C" {
NEW
32
        static void testHook(lua_State *L, lua_Debug *ar) {
×
NEW
33
        }
×
34
}
35

36
class TestStatePool : public ::testing::Test {
37
protected:
38
        void SetUp() override {
46✔
39
        }
46✔
40

41
        void TearDown() override {
46✔
42
        }
46✔
43
};
44

45
TEST_F(TestStatePool, CreatePoolWithDefaultConfig) {
8✔
46
        PoolConfig config;
4✔
47
        config.maxSize = 3;
2✔
48

49
        StatePool pool("test", config);
6✔
50

51
        EXPECT_EQ("test", pool.getColor());
2✔
52
        EXPECT_EQ(3u, pool.getMaxSize());
2✔
53
        EXPECT_EQ(0u, pool.getCurrentSize());
2✔
54
        EXPECT_EQ(0u, pool.availableCount());
2✔
55
        EXPECT_EQ(0u, pool.checkedOutCount());
2✔
56
        EXPECT_FALSE(pool.isThreadSafe());
2✔
57
}
2✔
58

59
TEST_F(TestStatePool, AcquireAndReleaseState) {
8✔
60
        PoolConfig config;
4✔
61
        config.maxSize = 2;
2✔
62

63
        StatePool pool("test", config);
6✔
64

65
        auto state = pool.acquire();
4✔
66
        EXPECT_NE(nullptr, state.get());
2✔
67
        EXPECT_EQ(1u, pool.getCurrentSize());
2✔
68
        EXPECT_EQ(0u, pool.availableCount());
2✔
69
        EXPECT_EQ(1u, pool.checkedOutCount());
2✔
70

71
        pool.release(std::move(state));
2✔
72
        EXPECT_EQ(1u, pool.getCurrentSize());
2✔
73
        EXPECT_EQ(1u, pool.availableCount());
2✔
74
        EXPECT_EQ(0u, pool.checkedOutCount());
2✔
75
}
2✔
76

77
TEST_F(TestStatePool, AcquireMultipleStates) {
8✔
78
        PoolConfig config;
4✔
79
        config.maxSize = 3;
2✔
80

81
        StatePool pool("test", config);
6✔
82

83
        auto state1 = pool.acquire();
4✔
84
        auto state2 = pool.acquire();
4✔
85
        auto state3 = pool.acquire();
4✔
86

87
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
88
        EXPECT_EQ(0u, pool.availableCount());
2✔
89
        EXPECT_EQ(3u, pool.checkedOutCount());
2✔
90

91
        pool.release(std::move(state1));
2✔
92
        pool.release(std::move(state2));
2✔
93
        pool.release(std::move(state3));
2✔
94

95
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
96
        EXPECT_EQ(3u, pool.availableCount());
2✔
97
        EXPECT_EQ(0u, pool.checkedOutCount());
2✔
98
}
2✔
99

100
TEST_F(TestStatePool, ReuseAvailableState) {
8✔
101
        PoolConfig config;
4✔
102
        config.maxSize = 2;
2✔
103

104
        StatePool pool("test", config);
6✔
105

106
        auto state1 = pool.acquire();
4✔
107
        lua_pushstring(*state1, "test_data");
2✔
108
        pool.release(std::move(state1));
2✔
109

110
        EXPECT_EQ(1u, pool.availableCount());
2✔
111

112
        auto state2 = pool.acquire();
2✔
113
        EXPECT_EQ(0u, lua_gettop(*state2));
2✔
114
        
115
        pool.release(std::move(state2));
2✔
116
}
2✔
117

118
TEST_F(TestStatePool, PoolExhausted) {
8✔
119
        PoolConfig config;
4✔
120
        config.maxSize = 1;
2✔
121
        config.exhaustionTimeoutMs = 1;
2✔
122

123
        StatePool pool("test", config);
6✔
124

125
        auto state1 = pool.acquire();
2✔
126
        EXPECT_NE(nullptr, state1.get());
2✔
127

128
        EXPECT_THROW(pool.acquire(), PoolExhaustedException);
4✔
129

130
        pool.release(std::move(state1));
2✔
131
}
2✔
132

133
TEST_F(TestStatePool, WarmupPool) {
8✔
134
        PoolConfig config;
4✔
135
        config.maxSize = 5;
2✔
136

137
        StatePool pool("test", config);
6✔
138
        pool.warmup(3);
2✔
139

140
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
141
        EXPECT_EQ(3u, pool.availableCount());
2✔
142

143
        auto state1 = pool.acquire();
4✔
144
        auto state2 = pool.acquire();
4✔
145
        auto state3 = pool.acquire();
2✔
146

147
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
148
        EXPECT_EQ(0u, pool.availableCount());
2✔
149

150
        pool.release(std::move(state1));
2✔
151
        pool.release(std::move(state2));
2✔
152
        pool.release(std::move(state3));
2✔
153
}
2✔
154

155
TEST_F(TestStatePool, WarmupDoesNotExceedMaxSize) {
8✔
156
        PoolConfig config;
4✔
157
        config.maxSize = 2;
2✔
158

159
        StatePool pool("test", config);
6✔
160
        pool.warmup(5);
2✔
161

162
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
163
        EXPECT_EQ(2u, pool.availableCount());
2✔
164
}
2✔
165

166
TEST_F(TestStatePool, DrainPool) {
8✔
167
        PoolConfig config;
4✔
168
        config.maxSize = 3;
2✔
169

170
        StatePool pool("test", config);
6✔
171
        pool.warmup(3);
2✔
172

173
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
174
        EXPECT_EQ(3u, pool.availableCount());
2✔
175

176
        pool.drain();
2✔
177

178
        EXPECT_EQ(0u, pool.getCurrentSize());
2✔
179
        EXPECT_EQ(0u, pool.availableCount());
2✔
180
}
2✔
181

182
TEST_F(TestStatePool, SetThreadSafe) {
8✔
183
        PoolConfig config;
4✔
184
        StatePool pool("test", config);
6✔
185

186
        EXPECT_FALSE(pool.isThreadSafe());
2✔
187

188
        pool.setThreadSafe(true);
2✔
189
        EXPECT_TRUE(pool.isThreadSafe());
2✔
190

191
        pool.setThreadSafe(false);
2✔
192
        EXPECT_FALSE(pool.isThreadSafe());
2✔
193
}
2✔
194

195
TEST_F(TestStatePool, AcquireFromAvailableQueue) {
8✔
196
        PoolConfig config;
4✔
197
        config.maxSize = 3;
2✔
198

199
        StatePool pool("test", config);
6✔
200

201
        pool.warmup(2);
2✔
202
        EXPECT_EQ(2u, pool.availableCount());
2✔
203

204
        auto state1 = pool.acquire();
4✔
205
        EXPECT_EQ(1u, pool.availableCount());
2✔
206
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
207

208
        auto state2 = pool.acquire();
4✔
209
        EXPECT_EQ(0u, pool.availableCount());
2✔
210

211
        auto state3 = pool.acquire();
2✔
212
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
213

214
        pool.release(std::move(state1));
2✔
215
        pool.release(std::move(state2));
2✔
216
        pool.release(std::move(state3));
2✔
217
}
2✔
218

219
TEST_F(TestStatePool, PoolConfigWithLibraries) {
8✔
220
        PoolConfig config;
4✔
221
        config.libraries = {"base", "math"};
6✔
222
        config.maxSize = 2;
2✔
223

224
        StatePool pool("test", config);
6✔
225

226
        auto state = pool.acquire();
2✔
227
        EXPECT_NE(nullptr, state.get());
2✔
228

229
        lua_getglobal(*state, "math");
2✔
230
        EXPECT_TRUE(lua_istable(*state, -1));
2✔
231
        lua_pop(*state, 1);
2✔
232

233
        lua_getglobal(*state, "io");
2✔
234
        EXPECT_TRUE(lua_isnil(*state, -1));
2✔
235
        lua_pop(*state, 1);
2✔
236

237
        lua_getglobal(*state, "os");
2✔
238
        EXPECT_TRUE(lua_isnil(*state, -1));
2✔
239
        lua_pop(*state, 1);
2✔
240

241
        lua_getglobal(*state, "string");
2✔
242
        EXPECT_TRUE(lua_isnil(*state, -1));
2✔
243
        lua_pop(*state, 1);
2✔
244

245
        lua_getglobal(*state, "print");
2✔
246
        EXPECT_TRUE(lua_isfunction(*state, -1));
2✔
247
        lua_pop(*state, 1);
2✔
248

249
        pool.release(std::move(state));
2✔
250
}
2✔
251

252
TEST_F(TestStatePool, PoolConfigWithGlobalVariables) {
8✔
253
        PoolConfig config;
4✔
254
        config.maxSize = 2;
2✔
255
        config.AddGlobalVariable("test_var", std::make_shared<LuaTNumber>(42.0));
2✔
256

257
        StatePool pool("test", config);
6✔
258

259
        auto state = pool.acquire();
2✔
260
        
261
        lua_getglobal(*state, "test_var");
2✔
262
        EXPECT_TRUE(lua_isnumber(*state, -1));
2✔
263
        EXPECT_DOUBLE_EQ(42.0, lua_tonumber(*state, -1));
2✔
264
        lua_pop(*state, 1);
2✔
265

266
        pool.release(std::move(state));
2✔
267
}
2✔
268

269
TEST_F(TestStatePool, PoolConfigWithHooks) {
8✔
270
        PoolConfig config;
4✔
271
        config.maxSize = 2;
2✔
272
        config.AddHook("call", 0, testHook);
2✔
273

274
        StatePool pool("test", config);
6✔
275

276
        auto state = pool.acquire();
2✔
277
        EXPECT_NE(nullptr, state.get());
2✔
278
        
279
        pool.release(std::move(state));
2✔
280
}
2✔
281

282
TEST_F(TestStatePool, ThreadSafeAcquireAndRelease) {
8✔
283
        PoolConfig config;
4✔
284
        config.maxSize = 3;
2✔
285

286
        StatePool pool("test", config);
6✔
287
        pool.setThreadSafe(true);
2✔
288

289
        auto state1 = pool.acquire();
4✔
290
        auto state2 = pool.acquire();
4✔
291

292
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
293
        EXPECT_EQ(2u, pool.checkedOutCount());
2✔
294

295
        pool.release(std::move(state1));
2✔
296
        pool.release(std::move(state2));
2✔
297

298
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
299
        EXPECT_EQ(2u, pool.availableCount());
2✔
300
        EXPECT_EQ(0u, pool.checkedOutCount());
2✔
301
}
2✔
302

303
TEST_F(TestStatePool, GetConfig) {
8✔
304
        PoolConfig config;
4✔
305
        config.maxSize = 7;
2✔
306

307
        StatePool pool("test", config);
6✔
308

309
        const PoolConfig& retrievedConfig = pool.getConfig();
2✔
310
        EXPECT_EQ(7u, retrievedConfig.maxSize);
2✔
311
}
2✔
312

313
TEST_F(TestStatePool, EmptyLibrariesLoadsAll) {
8✔
314
        PoolConfig config;
4✔
315
        config.libraries = {};
2✔
316
        config.maxSize = 1;
2✔
317

318
        StatePool pool("test", config);
6✔
319

320
        auto state = pool.acquire();
2✔
321
        
322
        lua_getglobal(*state, "io");
2✔
323
        EXPECT_TRUE(lua_istable(*state, -1));
2✔
324
        lua_pop(*state, 1);
2✔
325

326
        lua_getglobal(*state, "math");
2✔
327
        EXPECT_TRUE(lua_istable(*state, -1));
2✔
328
        lua_pop(*state, 1);
2✔
329

330
        lua_getglobal(*state, "os");
2✔
331
        EXPECT_TRUE(lua_istable(*state, -1));
2✔
332
        lua_pop(*state, 1);
2✔
333

334
        pool.release(std::move(state));
2✔
335
}
2✔
336

337
TEST_F(TestStatePool, PoolConfigFluentAPI) {
8✔
338
        PoolConfig config = PoolConfig()
2✔
339
                .SetLibraries({"base"})
8✔
340
                .SetMaxSize(3)
2✔
341
                .SetExhaustionTimeoutMs(100);
4✔
342

343
        EXPECT_EQ(3u, config.maxSize);
2✔
344
        EXPECT_EQ(100u, config.exhaustionTimeoutMs);
2✔
345
        EXPECT_EQ(1u, config.libraries.size());
2✔
346
}
2✔
347

348
TEST_F(TestStatePool, PoolConfigWithLibrariesWithoutBase) {
8✔
349
        PoolConfig config;
4✔
350
        config.libraries = {"math"};
4✔
351
        config.maxSize = 2;
2✔
352

353
        StatePool pool("test", config);
6✔
354

355
        auto state = pool.acquire();
2✔
356
        EXPECT_NE(nullptr, state.get());
2✔
357

358
        lua_getglobal(*state, "math");
2✔
359
        EXPECT_TRUE(lua_istable(*state, -1));
2✔
360
        lua_pop(*state, 1);
2✔
361

362
        lua_getglobal(*state, "print");
2✔
363
        EXPECT_TRUE(lua_isfunction(*state, -1));
2✔
364
        lua_pop(*state, 1);
2✔
365

366
        pool.release(std::move(state));
2✔
367
}
2✔
368

369
TEST_F(TestStatePool, PoolConfigWithHookTypes) {
8✔
370
        PoolConfig config;
4✔
371
        config.maxSize = 4;
2✔
372
        config.AddHook("call", 0, testHook);
2✔
373
        config.AddHook("return", 0, testHook);
2✔
374
        config.AddHook("line", 0, testHook);
2✔
375
        config.AddHook("count", 1, testHook);
2✔
376

377
        StatePool pool("test", config);
6✔
378

379
        auto state = pool.acquire();
2✔
380
        EXPECT_NE(nullptr, state.get());
2✔
381
        
382
        pool.release(std::move(state));
2✔
383
}
2✔
384

385
TEST_F(TestStatePool, ThreadSafeWarmup) {
8✔
386
        PoolConfig config;
4✔
387
        config.maxSize = 3;
2✔
388

389
        StatePool pool("test", config);
6✔
390
        pool.setThreadSafe(true);
2✔
391
        
392
        EXPECT_EQ(0u, pool.getCurrentSize());
2✔
393
        
394
        pool.warmup(2);
2✔
395
        
396
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
397
        EXPECT_EQ(2u, pool.availableCount());
2✔
398
}
2✔
399

400
TEST_F(TestStatePool, ThreadSafeStatsMethods) {
8✔
401
        PoolConfig config;
4✔
402
        config.maxSize = 3;
2✔
403

404
        StatePool pool("test", config);
6✔
405
        pool.setThreadSafe(true);
2✔
406
        
407
        EXPECT_EQ(0u, pool.getCurrentSize());
2✔
408
        EXPECT_EQ(0u, pool.availableCount());
2✔
409
        
410
        pool.warmup(2);
2✔
411
        
412
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
413
        EXPECT_EQ(2u, pool.availableCount());
2✔
414
        
415
        auto state = pool.acquire();
2✔
416
        
417
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
418
        EXPECT_EQ(1u, pool.availableCount());
2✔
419
        EXPECT_EQ(1u, pool.checkedOutCount());
2✔
420
        
421
        pool.release(std::move(state));
2✔
422
}
2✔
423

424
TEST_F(TestStatePool, ThreadSafeDrain) {
8✔
425
        PoolConfig config;
4✔
426
        config.maxSize = 3;
2✔
427

428
        StatePool pool("test", config);
6✔
429
        pool.setThreadSafe(true);
2✔
430
        
431
        pool.warmup(3);
2✔
432
        EXPECT_EQ(3u, pool.getCurrentSize());
2✔
433
        
434
        pool.drain();
2✔
435
        
436
        EXPECT_EQ(0u, pool.getCurrentSize());
2✔
437
        EXPECT_EQ(0u, pool.availableCount());
2✔
438
}
2✔
439

440
TEST_F(TestStatePool, AcquireFromAvailableQueueThreadSafe) {
8✔
441
        PoolConfig config;
4✔
442
        config.maxSize = 3;
2✔
443

444
        StatePool pool("test", config);
6✔
445
        pool.setThreadSafe(true);
2✔
446

447
        pool.warmup(2);
2✔
448
        EXPECT_EQ(2u, pool.availableCount());
2✔
449

450
        auto state1 = pool.acquire();
4✔
451
        EXPECT_EQ(1u, pool.availableCount());
2✔
452
        EXPECT_EQ(2u, pool.getCurrentSize());
2✔
453

454
        pool.release(std::move(state1));
2✔
455
        EXPECT_EQ(2u, pool.availableCount());
2✔
456

457
        auto state2 = pool.acquire();
2✔
458
        EXPECT_EQ(1u, pool.availableCount());
2✔
459
        
460
        pool.release(std::move(state2));
2✔
461
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc