Skip to content

Commit 789146f

Browse files
committed
Release GIL in
1 parent b1c6ab7 commit 789146f

2 files changed

Lines changed: 67 additions & 6 deletions

File tree

src/export_cache.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ cache_t* pypluginCache_init(
107107
py::function cache_init_hook, py::function cache_hit_hook,
108108
py::function cache_miss_hook, py::function cache_eviction_hook,
109109
py::function cache_remove_hook, py::function cache_free_hook) {
110+
py::gil_scoped_acquire acquire;
110111
// Initialize base cache structure with exception safety
111112
cache_t* cache = nullptr;
112113
std::unique_ptr<pypluginCache_params_t, PypluginCacheParamsDeleter> params;
@@ -163,19 +164,26 @@ cache_t* pypluginCache_init(
163164
}
164165

165166
static void pypluginCache_free(cache_t* cache) {
166-
if (!cache || !cache->eviction_params) {
167+
if (!cache) {
168+
return;
169+
}
170+
if (!cache->eviction_params) {
171+
// No params, just free the cache structure
172+
cache_struct_free(cache);
167173
return;
168174
}
169175

170-
// Use smart pointer for automatic cleanup
171-
std::unique_ptr<pypluginCache_params_t, PypluginCacheParamsDeleter> params(
172-
static_cast<pypluginCache_params_t*>(cache->eviction_params));
173-
176+
auto* raw_params = static_cast<pypluginCache_params_t*>(cache->eviction_params);
177+
cache->eviction_params = nullptr;
174178
// The smart pointer destructor will handle cleanup automatically
179+
std::unique_ptr<pypluginCache_params_t, PypluginCacheParamsDeleter> params(raw_params);
180+
params.reset();
181+
175182
cache_struct_free(cache);
176183
}
177184

178185
static bool pypluginCache_get(cache_t* cache, const request_t* req) {
186+
py::gil_scoped_acquire acquire;
179187
bool hit = cache_get_base(cache, req);
180188
pypluginCache_params_t* params =
181189
(pypluginCache_params_t*)cache->eviction_params;
@@ -204,6 +212,7 @@ static cache_obj_t* pypluginCache_to_evict(cache_t* cache,
204212
}
205213

206214
static void pypluginCache_evict(cache_t* cache, const request_t* req) {
215+
py::gil_scoped_acquire acquire;
207216
pypluginCache_params_t* params =
208217
(pypluginCache_params_t*)cache->eviction_params;
209218

@@ -223,6 +232,7 @@ static void pypluginCache_evict(cache_t* cache, const request_t* req) {
223232
}
224233

225234
static bool pypluginCache_remove(cache_t* cache, const obj_id_t obj_id) {
235+
py::gil_scoped_acquire acquire;
226236
pypluginCache_params_t* params =
227237
(pypluginCache_params_t*)cache->eviction_params;
228238

@@ -568,7 +578,8 @@ void export_cache(py::module& m) {
568578
bytes_req > 0 ? 1.0 - (double)bytes_hit / bytes_req : 0.0;
569579
return std::make_tuple(obj_miss_ratio, byte_miss_ratio);
570580
},
571-
"cache"_a, "reader"_a, "start_req"_a = 0, "max_req"_a = -1);
581+
"cache"_a, "reader"_a, "start_req"_a = 0, "max_req"_a = -1,
582+
py::call_guard<py::gil_scoped_release>());
572583
}
573584

574585
} // namespace libcachesim

tests/test_gil.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import pytest
2+
import libcachesim as lcs
3+
import threading
4+
import time
5+
6+
S3_URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst"
7+
8+
def run_heavy_simulation(name):
9+
# Create a large synthetic trace
10+
reader = lcs.TraceReader(trace=S3_URI)
11+
cache = lcs.LRU(cache_size=1024*1024)
12+
13+
print(f"Thread {name} starting simulation...")
14+
start = time.time()
15+
# Call C++ core logic
16+
lcs.Util.process_trace(cache, reader)
17+
end = time.time()
18+
print(f"Thread {name} completed in {end - start:.2f}s")
19+
20+
def test_gil_release():
21+
"""
22+
Test to verify that the GIL is released during heavy C++ processing.
23+
We run two threads that perform heavy simulations and measure total time.
24+
If the total time is close to the single-thread time, it indicates GIL release.
25+
If the total time is close to double the single-thread time, it indicates GIL is still held.
26+
"""
27+
# --- Experiment start ---
28+
29+
# test single-thread time for reference
30+
start_single = time.time()
31+
run_heavy_simulation("Single")
32+
end_single = time.time()
33+
single_thread_time = end_single - start_single
34+
print(f"\nSingle-thread time: {single_thread_time:.2f}s")
35+
36+
start_total = time.time()
37+
38+
t1 = threading.Thread(target=run_heavy_simulation, args=("A",))
39+
t2 = threading.Thread(target=run_heavy_simulation, args=("B",))
40+
41+
t1.start()
42+
t2.start()
43+
44+
t1.join()
45+
t2.join()
46+
47+
end_total = time.time()
48+
print(f"\nTotal elapsed time: {end_total - start_total:.2f}s")
49+
50+
assert single_thread_time * 1.5 > (end_total - start_total), "GIL release test failed: Total time should be close to single-thread time if GIL is released."

0 commit comments

Comments
 (0)