rulegen cannot start any worker on macOS/Windows (spawn + PicklingError), plus worker crash and shutdown ordering - #2
Merged
Conversation
…order rulegen cannot start a single worker on macOS or Windows. Since Python 3.8 multiprocessing defaults to the 'spawn' start method there, which pickles the RuleGen instance to reach each worker, and the instance holds a dict of lambdas (the hashcat rule engine) plus a native Enchant handle. Every Process() call dies with PicklingError before any analysis runs. The rule engine moves into build_rule_engine() and __getstate__/__setstate__ drop the unpicklable attributes so the child rebuilds them. A source word with no surviving rules crashed the worker on an empty-list index; generate_hashcat_rules returns [] whenever nothing survives the max_rule_len filter. The worker's except clause only catches KeyboardInterrupt and SystemExit, so it died and the bounded feeder queue could then block the parent. while not passwords_queue.empty() treated a drained input queue as "work finished", but it only means the last password was taken, not analyzed, so the writers could be stopped before the workers finished enqueuing. Shutdown now stops the analysis workers, joins them, then pills and joins the writers. Ctrl-C is documented as the way to end a run early and still generate statistics, but a single SIGINT did not stop the read loop. It is now handled as a request to stop reading, and shutdown runs on every path. statsgen: the Advanced Masks section ignored --hiderare and always hid entries under 1%; it is now gated like every other section.
Owner
|
Thanks for this — really appreciate the depth here! I tested your branch before merging: ran it end-to-end under Windows spawn (no PicklingError, all output files complete, no orphan processes), and ran your test_pack.py suite from bandrel/pack against it — everything covering these five issues passes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nice work on the modernization pass — I went looking for silent-failure bugs
across the PACK forks and yours had already fixed the maskgen filter directions,
the missing
?b/?h/?H/?1-?4charsets, theword_counter_totaldenominator and the
'/xrule key collision. These are the ones I still found,ported onto your
masterand tested against it.rulegen cannot start at all on macOS or Windows
Since Python 3.8,
multiprocessingdefaults to the spawn start method onthose platforms. Spawn pickles the
RuleGeninstance to reach each worker, andthe instance holds
self.hashcat_rule(a dict of lambdas) plus a native Enchanthandle — neither is picklable, so every
Process()call dies before any analysisruns:
Verified on macOS with Python 3.14 and pyenchant 3.3.0, where
multiprocessing.get_start_method()isspawn. Fix: the rule engine, leet mapand preanalysis rules move into
build_rule_engine(), and__getstate__/__setstate__drop the unpicklable attributes so the childrebuilds them. A custom
--wordlistis recorded so workers rebuild the samedictionary instead of silently falling back to the system one. With this applied,
a real run completes end to end on your code under spawn.
Worker crash on words with no rules
generate_hashcat_rulesreturns[]whenever nothing survives themax_rule_lenfilter — easy to reach with a small--maxrulelen. Indexing[0]raises
IndexErrorinsidepassword_worker, whoseexceptonly catchesKeyboardInterruptandSystemExit. The worker dies, and becausepasswords_queueis bounded to the thread count the parent can then blockforever on
put()once enough consumers are gone. The visible symptom is a runthat stops making progress with no error summary.
Shutdown ordering drops the tail of the analysis
while not passwords_queue.empty()treats a drained input queue as "workfinished", but it only means the last password was taken, not analyzed. The
parent then pushes
Noneontorules_queue, whichrule_workercan consumebefore the still-running workers finish enqueuing their rules. Shutdown now stops
the analysis workers and joins them, then pills and joins the writers, so the
output files are complete and closed before being read back.
Death pills are delivered with retries rather than abandoned on the first
queue.Full, since the bounded input queue is normally full on an early exit.Ctrl-C did not end a run
analyze_passwords_fileadvertises "Press Ctrl-C to end execution and generatestatistical analysis", but the
try/except/elsemeans an interrupt skips theelseentirely, so no death pills are sent and the children are leaked. Inpractice a single SIGINT did not stop the read loop at all. SIGINT is now handled
as a request to stop reading and shutdown runs from a
finally, on every path.Tested on your code: interrupting a 30,000-password run stops after ~2,600
passwords, shuts down in under a second, writes all four output files and leaves
no orphan processes.
statsgen: Advanced Masks ignored
--hiderareThat section hardcodes
if count*100//self.filter_counter > 0while the otherthree gate on
self.hiderare, so sub-1% advanced masks were always hidden fromthe display regardless of the flag. Now gated consistently; the output file was
and remains complete.
Testing
Regression tests covering these (plus the maskgen items you'd already fixed) are
in bandrel/pack as
tests/test_pack.py. Ileft them out of this PR since they're written against a different base — happy
to port them here if you'd like.
Related: Hydraze#9, Hydraze#10, iphelix#31.