Commit c2ccca0e authored by Vitaly Lipatov's avatar Vitaly Lipatov

route-web-api: add thread lock for concurrent list operations

Protect read-modify-write cycles in handle_add/remove/move and consistent reads in /api/list with _list_lock to prevent data loss when multiple users modify lists simultaneously. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent b87a3775
...@@ -54,6 +54,7 @@ CHECK_GATEWAYS = [ ...@@ -54,6 +54,7 @@ CHECK_GATEWAYS = [
CHECK_TIMEOUT = 10 CHECK_TIMEOUT = 10
_check_lock = threading.Lock() _check_lock = threading.Lock()
_list_lock = threading.Lock()
def _check_one(name, proxy, url, ipver="-4"): def _check_one(name, proxy, url, ipver="-4"):
...@@ -1041,6 +1042,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1041,6 +1042,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_json(OPENAPI_SPEC) self.send_json(OPENAPI_SPEC)
elif path == "/api/list": elif path == "/api/list":
with _list_lock:
data = {} data = {}
for mode, fpath in LIST_FILES.items(): for mode, fpath in LIST_FILES.items():
data[mode] = read_list(fpath) data[mode] = read_list(fpath)
...@@ -1118,6 +1120,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1118,6 +1120,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_error_json(400, "Invalid domain or IP") self.send_error_json(400, "Invalid domain or IP")
return return
with _list_lock:
# Check if already in the same list # Check if already in the same list
entries = read_list(LIST_FILES[mode]) entries = read_list(LIST_FILES[mode])
if domain in entries: if domain in entries:
...@@ -1142,6 +1145,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1142,6 +1145,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
entries.append(domain) entries.append(domain)
write_list(LIST_FILES[mode], entries) write_list(LIST_FILES[mode], entries)
if moved_from: if moved_from:
self.log_message("MOVE %s: %s -> %s", domain, moved_from, mode) self.log_message("MOVE %s: %s -> %s", domain, moved_from, mode)
else: else:
...@@ -1159,6 +1163,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1159,6 +1163,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_error_json(400, "Invalid domain or IP") self.send_error_json(400, "Invalid domain or IP")
return return
with _list_lock:
entries = read_list(LIST_FILES[mode]) entries = read_list(LIST_FILES[mode])
if domain not in entries: if domain not in entries:
self.send_error_json(404, "Not found in %s list" % mode) self.send_error_json(404, "Not found in %s list" % mode)
...@@ -1166,6 +1171,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1166,6 +1171,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
entries.remove(domain) entries.remove(domain)
write_list(LIST_FILES[mode], entries) write_list(LIST_FILES[mode], entries)
self.log_message("REMOVE %s from %s", domain, mode) self.log_message("REMOVE %s from %s", domain, mode)
self.send_json({"ok": True, "domain": domain, "mode": mode}) self.send_json({"ok": True, "domain": domain, "mode": mode})
...@@ -1181,6 +1187,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1181,6 +1187,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_error_json(400, "Invalid domain or IP") self.send_error_json(400, "Invalid domain or IP")
return return
with _list_lock:
src_entries = read_list(LIST_FILES[src]) src_entries = read_list(LIST_FILES[src])
if domain not in src_entries: if domain not in src_entries:
self.send_error_json(404, "Not found in %s list" % src) self.send_error_json(404, "Not found in %s list" % src)
...@@ -1195,6 +1202,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler): ...@@ -1195,6 +1202,7 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
dst_entries.append(domain) dst_entries.append(domain)
write_list(LIST_FILES[src], src_entries) write_list(LIST_FILES[src], src_entries)
write_list(LIST_FILES[dst], dst_entries) write_list(LIST_FILES[dst], dst_entries)
self.log_message("MOVE %s: %s -> %s", domain, src, dst) self.log_message("MOVE %s: %s -> %s", domain, src, dst)
self.send_json({"ok": True, "domain": domain, "from": src, "to": dst}) self.send_json({"ok": True, "domain": domain, "from": src, "to": dst})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment