| 201 | } |
| 202 | |
| 203 | func (pool *BrutePool) Run(offset, limit int) { |
| 204 | pool.Worder.Run() |
| 205 | if pool.Active { |
| 206 | pool.launchProducer(pool.doActive) |
| 207 | } |
| 208 | |
| 209 | if pool.Bak { |
| 210 | pool.launchProducer(pool.doBak) |
| 211 | } |
| 212 | |
| 213 | if pool.Fuzzuli { |
| 214 | pool.launchProducer(pool.doFuzzuli) |
| 215 | } |
| 216 | |
| 217 | if pool.Common { |
| 218 | pool.launchProducer(pool.doCommonFile) |
| 219 | } |
| 220 | |
| 221 | // worderCh 指向 Worder 输出; 设为 nil 后 select 不再接受新词, |
| 222 | // 随后 allDone 等待 in-flight 工作全部结束再退出循环. |
| 223 | // 这比 atomic.Bool + 轮询 monitor goroutine 更安全: wg.Add 只发生在 |
| 224 | // worderCh 分支, nil 掉之后不会再有新 Add, 消除了 Add/Wait 竞态. |
| 225 | worderCh := pool.Worder.Output |
| 226 | var allDone <-chan struct{} // nil channel: select 永远不选中 |
| 227 | |
| 228 | var drainOnce sync.Once |
| 229 | startDrain := func() { |
| 230 | drainOnce.Do(func() { |
| 231 | worderCh = nil |
| 232 | ch := make(chan struct{}) |
| 233 | go func() { pool.wg.Wait(); close(ch) }() |
| 234 | allDone = ch |
| 235 | }) |
| 236 | } |
| 237 | |
| 238 | Loop: |
| 239 | for { |
| 240 | select { |
| 241 | case w, ok := <-worderCh: |
| 242 | if !ok { |
| 243 | startDrain() |
| 244 | continue |
| 245 | } |
| 246 | pool.Statistor.End++ |
| 247 | |
| 248 | wordOffset := int(pool.wordOffset.Add(1)) |
| 249 | if wordOffset < offset { |
| 250 | continue |
| 251 | } |
| 252 | |
| 253 | if pool.Statistor.End > limit { |
| 254 | startDrain() |
| 255 | continue |
| 256 | } |
| 257 | |
| 258 | if w == "" { |
| 259 | pool.Statistor.Skipped++ |
| 260 | pool.Bar.Done() |