Skip to content

Commit 821e651

Browse files
committed
Fix multi-threading issues in routes object and add test case
1 parent f436804 commit 821e651

2 files changed

Lines changed: 97 additions & 9 deletions

File tree

aikido_zen/background_process/routes/__init__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,19 @@ def increment_route(self, route_metadata):
4141
self.initialize_route(route_metadata)
4242
# Add hits to
4343
route = self.routes.get(key)
44-
route["hits"] += 1
45-
route["hits_delta_since_sync"] += 1
44+
if route:
45+
route["hits"] += 1
46+
route["hits_delta_since_sync"] += 1
4647

4748
def update_route_with_apispec(self, route_metadata, apispec):
4849
"""
4950
Updates apispec of a given route (or creates it).
5051
route_metadata object includes route, url and method
5152
"""
5253
key = route_to_key(route_metadata)
53-
if not self.routes.get(key):
54-
return
55-
update_route_info(apispec, self.routes[key])
54+
route = self.routes.get(key)
55+
if route:
56+
update_route_info(apispec, route)
5657

5758
def get(self, route_metadata):
5859
"""Gets you the route entry if it exists using route metadata"""
@@ -61,8 +62,8 @@ def get(self, route_metadata):
6162

6263
def get_routes_with_hits(self):
6364
"""Gets you all routes with a positive hits delta"""
64-
result = dict()
65-
for key, route in self.routes.items():
65+
result = {}
66+
for key, route in list(self.routes.items()):
6667
if route["hits_delta_since_sync"] <= 0:
6768
continue # do not add routes without a hit delta
6869
result[key] = route
@@ -78,11 +79,13 @@ def manage_routes_size(self):
7879
"""
7980
if len(self.routes) >= self.max_size:
8081
least_used = [None, float("inf")]
81-
for key, route in self.routes.items():
82+
for key, route in list(self.routes.items()):
8283
if route.get("hits") < least_used[1]:
8384
least_used = [key, route.get("hits")]
8485
if least_used[0]:
85-
del self.routes[least_used[0]]
86+
delete_route = self.routes[least_used[0]]
87+
if delete_route:
88+
del delete_route
8689

8790
def __iter__(self):
8891
return iter(self.routes.values())
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import random
3+
import sys
4+
import threading
5+
import time
6+
7+
from aikido_zen.background_process.routes import Routes
8+
from aikido_zen.helpers.logging import logger
9+
10+
11+
def add_routes(routes, num_routes, error_count):
12+
"""Function to add routes to the Routes object."""
13+
try:
14+
for _ in range(num_routes):
15+
route_metadata = {
16+
"method": random.choice(["GET", "POST", "PUT", "DELETE"]),
17+
"route": f"/api/test/{random.randint(1, 10000)}",
18+
}
19+
routes.increment_route(route_metadata)
20+
except Exception:
21+
error_count["errors_add"] += 1
22+
23+
24+
def export_routes(routes, error_count):
25+
"""Function to export routes with hits."""
26+
while True:
27+
try:
28+
routes.get_routes_with_hits()
29+
except Exception:
30+
error_count["errors_export"] += 1
31+
32+
33+
def runtime_error_without_data_lock():
34+
"""Test function to check for runtime errors without data locking."""
35+
routes = Routes(max_size=5000)
36+
37+
# Create threads for adding routes and exporting routes
38+
num_add_threads = 30
39+
num_routes_per_thread = 3000
40+
num_export_threads = 20
41+
error_count = {
42+
"errors_add": 0,
43+
"errors_export": 0,
44+
}
45+
46+
add_threads = []
47+
for i in range(num_add_threads):
48+
print(f"Adding thread {i} started")
49+
thread = threading.Thread(
50+
target=add_routes,
51+
args=(routes, num_routes_per_thread, error_count),
52+
daemon=True,
53+
)
54+
add_threads.append(thread)
55+
thread.start()
56+
57+
# Start the export thread
58+
export_threads = []
59+
for i in range(num_export_threads):
60+
print(f"Export thread {i} started")
61+
thread = threading.Thread(
62+
target=export_routes, args=(routes, error_count), daemon=True
63+
)
64+
export_threads.append(thread)
65+
thread.start()
66+
67+
# Wait for all add threads to finish
68+
time.sleep(1)
69+
70+
for thread in add_threads:
71+
print("Closing add thread: " + thread.name)
72+
thread.join(timeout=20 / 1000)
73+
74+
# Stop the export thread after adding routes
75+
for thread in export_threads:
76+
print("Closing export thread: " + thread.name)
77+
thread.join(timeout=20 / 1000)
78+
79+
print(error_count)
80+
assert error_count["errors_add"] == 0
81+
assert error_count["errors_export"] == 0
82+
83+
84+
if __name__ == "__main__":
85+
runtime_error_without_data_lock()

0 commit comments

Comments
 (0)