Skip to content

Commit 5861eed

Browse files
committed
Potential cluster fix & Browser close fix
Keyword "potentially"
1 parent 804382d commit 5861eed

3 files changed

Lines changed: 67 additions & 39 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
[![Latest Build](https://img.shields.io/github/actions/workflow/status/TheNetsky/Microsoft-Rewards-Script/auto-release.yml?branch=v3&style=for-the-badge&label=Latest%20Build)](https://github.com/TheNetsky/Microsoft-Rewards-Script/actions/workflows/auto-release.yml)
33
[![Docker](https://img.shields.io/badge/Docker-GHCR-blue?style=for-the-badge&logo=docker)](https://github.com/TheNetsky/Microsoft-Rewards-Script/pkgs/container/microsoft-rewards-script)
44

5-
65
> [!CAUTION]
76
> V3.x does not support the new Bing Rewards interface!
87
>
@@ -27,6 +26,7 @@
2726
Works on Windows, Linux, macOS, and WSL.
2827

2928
### Get the script
29+
3030
```bash
3131
git clone https://github.com/TheNetsky/Microsoft-Rewards-Script.git
3232
cd Microsoft-Rewards-Script
@@ -35,7 +35,7 @@ cd Microsoft-Rewards-Script
3535
Or, download the latest release ZIP and extract it.
3636

3737
> [!TIP]
38-
> **Docker users:** optionally skip the clone step when using the prebuilt image. You only need a valid `accounts.json` and `config.json` locally. They can be placed anywhere.
38+
> **Docker users:** optionally skip the clone step when using the prebuilt image. You only need a valid `accounts.json` and `config.json` locally. They can be placed anywhere.
3939
> Update the `volumes` section in [`compose.yaml`](./compose.yaml) to point to your files (e.g., `/your/path/to/accounts.json:/usr/src/microsoft-rewards-script/dist/accounts.json:ro`).
4040
4141
### Create accounts.json and config.json
@@ -53,13 +53,15 @@ Copy, rename, and edit your account and configuration files before deploying the
5353
> You must rebuild your script after making any changes to accounts.json and config.json.
5454
5555
### Build and run the script (bare metal)
56+
5657
```bash
5758
npm run pre-build
5859
npm run build
5960
npm run start
6061
```
6162

6263
### Build and run the script (Docker)
64+
6365
```bash
6466
docker compose up -d
6567
```

src/browser/BrowserFunc.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -293,29 +293,33 @@ export default class BrowserFunc {
293293
}
294294

295295
async closeBrowser(browser: BrowserContext, email: string) {
296+
const rootBrowser = (browser as any).browser?.() || null
297+
296298
try {
299+
// Try to save cookies
297300
const cookies = await browser.cookies()
298-
299-
// Save cookies
300-
this.bot.logger.debug(
301-
this.bot.isMobile,
302-
'CLOSE-BROWSER',
303-
`Saving ${cookies.length} cookies to session folder!`
304-
)
301+
this.bot.logger.debug(this.bot.isMobile, 'CLOSE-BROWSER', `Saving ${cookies.length} cookies.`)
305302
await saveSessionData(this.bot.config.sessionPath, cookies, email, this.bot.isMobile)
306303

307304
await this.bot.utils.wait(2000)
308-
309-
// Close browser
310-
await browser.close()
311-
this.bot.logger.info(this.bot.isMobile, 'CLOSE-BROWSER', 'Browser closed cleanly!')
312305
} catch (error) {
313-
this.bot.logger.error(
314-
this.bot.isMobile,
315-
'CLOSE-BROWSER',
316-
`An error occurred: ${error instanceof Error ? error.message : String(error)}`
317-
)
318-
throw error
306+
this.bot.logger.error(this.bot.isMobile, 'CLOSE-BROWSER', `Failed to save session: ${error}`)
307+
} finally {
308+
try {
309+
await browser.close()
310+
311+
if (rootBrowser) {
312+
await rootBrowser.close().catch(() => {})
313+
}
314+
315+
this.bot.logger.info(this.bot.isMobile, 'CLOSE-BROWSER', 'All browser resources closed.')
316+
} catch (closeError) {
317+
this.bot.logger.warn(
318+
this.bot.isMobile,
319+
'CLOSE-BROWSER',
320+
'Shutdown encountered an error, but process exiting.'
321+
)
322+
}
319323
}
320324
}
321325

src/index.ts

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class MicrosoftRewardsBot {
142142

143143
if (this.config.clusters > 1) {
144144
if (cluster.isPrimary) {
145-
this.runMaster(runStartTime)
145+
await this.runMaster(runStartTime)
146146
} else {
147147
this.runWorker(runStartTime)
148148
}
@@ -151,14 +151,15 @@ export class MicrosoftRewardsBot {
151151
}
152152
}
153153

154-
private runMaster(runStartTime: number): void {
154+
private async runMaster(runStartTime: number): Promise<void> {
155155
void this.logger.info('main', 'CLUSTER-PRIMARY', `Primary process started | PID: ${process.pid}`)
156156

157157
const rawChunks = this.utils.chunkArray(this.accounts, this.config.clusters)
158158
const accountChunks = rawChunks.filter(c => c && c.length > 0)
159159
this.activeWorkers = accountChunks.length
160160

161161
const allAccountStats: AccountStats[] = []
162+
let hadWorkerFailure = false
162163

163164
for (const chunk of accountChunks) {
164165
const worker = cluster.fork()
@@ -170,12 +171,11 @@ export class MicrosoftRewardsBot {
170171
}
171172

172173
const log = msg.__ipcLog
173-
174174
if (log && typeof log.content === 'string') {
175-
const config = this.config
176-
const webhook = config.webhook
177-
const content = log.content
178-
const level = log.level
175+
const { webhook } = this.config
176+
const { content, level } = log
177+
178+
// Webhooks, for later expansion?
179179
if (webhook.discord?.enabled && webhook.discord.url) {
180180
sendDiscord(webhook.discord.url, content, level)
181181
}
@@ -184,23 +184,35 @@ export class MicrosoftRewardsBot {
184184
}
185185
}
186186
})
187+
188+
// Startup delay for clusters due to resource usage
189+
if (accountChunks.indexOf(chunk) !== accountChunks.length - 1) {
190+
await this.utils.wait(5000)
191+
}
187192
}
188193

189-
const onWorkerDone = async (label: 'exit' | 'disconnect', worker: Worker, code?: number): Promise<void> => {
194+
const onWorkerExit = async (worker: Worker, code?: number, signal?: string): Promise<void> => {
190195
const { pid } = worker.process
191-
this.activeWorkers -= 1
192196

193197
if (!pid || this.exitedWorkers.includes(pid)) {
194198
return
195-
} else {
196-
this.exitedWorkers.push(pid)
199+
}
200+
201+
this.exitedWorkers.push(pid)
202+
this.activeWorkers -= 1
203+
204+
// exit 0 = good, exit 1 = crash
205+
const failed = (code ?? 0) !== 0 || Boolean(signal)
206+
if (failed) {
207+
hadWorkerFailure = true
197208
}
198209

199210
this.logger.warn(
200211
'main',
201-
`CLUSTER-WORKER-${label.toUpperCase()}`,
202-
`Worker ${worker.process?.pid ?? '?'} ${label} | Code: ${code ?? 'n/a'} | Active workers: ${this.activeWorkers}`
212+
'CLUSTER-WORKER-EXIT',
213+
`Worker ${pid} exit | Code: ${code ?? 'n/a'} | Signal: ${signal ?? 'n/a'} | Active workers: ${this.activeWorkers}`
203214
)
215+
204216
if (this.activeWorkers <= 0) {
205217
const totalCollectedPoints = allAccountStats.reduce((sum, s) => sum + s.collectedPoints, 0)
206218
const totalInitialPoints = allAccountStats.reduce((sum, s) => sum + s.initialPoints, 0)
@@ -213,40 +225,50 @@ export class MicrosoftRewardsBot {
213225
`Completed all accounts | Accounts processed: ${allAccountStats.length} | Total points collected: +${totalCollectedPoints} | Old total: ${totalInitialPoints} → New total: ${totalFinalPoints} | Total runtime: ${totalDurationMinutes}min`,
214226
'green'
215227
)
228+
216229
await flushAllWebhooks()
217-
process.exit(code ?? 0)
230+
231+
process.exit(hadWorkerFailure ? 1 : 0)
218232
}
219233
}
220234

221-
cluster.on('exit', (worker, code) => {
222-
void onWorkerDone('exit', worker, code)
235+
cluster.on('exit', (worker, code, signal) => {
236+
void onWorkerExit(worker, code ?? undefined, signal ?? undefined)
223237
})
238+
224239
cluster.on('disconnect', worker => {
225-
void onWorkerDone('disconnect', worker, undefined)
240+
const pid = worker.process?.pid
241+
this.logger.warn('main', 'CLUSTER-WORKER-DISCONNECT', `Worker ${pid ?? '?'} disconnected`) // <-- Warning only
226242
})
227243
}
228244

229245
private runWorker(runStartTimeFromMaster?: number): void {
230246
void this.logger.info('main', 'CLUSTER-WORKER-START', `Worker spawned | PID: ${process.pid}`)
247+
231248
process.on('message', async ({ chunk, runStartTime }: { chunk: Account[]; runStartTime: number }) => {
232249
void this.logger.info(
233250
'main',
234251
'CLUSTER-WORKER-TASK',
235252
`Worker ${process.pid} received ${chunk.length} accounts.`
236253
)
254+
237255
try {
238256
const stats = await this.runTasks(chunk, runStartTime ?? runStartTimeFromMaster ?? Date.now())
257+
258+
// Send and flush before exit
239259
if (process.send) {
240260
process.send({ __stats: stats })
241261
}
242262

243-
process.disconnect()
263+
await flushAllWebhooks()
264+
process.exit(0)
244265
} catch (error) {
245266
this.logger.error(
246267
'main',
247268
'CLUSTER-WORKER-ERROR',
248269
`Worker task crash: ${error instanceof Error ? error.message : String(error)}`
249270
)
271+
250272
await flushAllWebhooks()
251273
process.exit(1)
252274
}
@@ -334,7 +356,7 @@ export class MicrosoftRewardsBot {
334356
}
335357
}
336358

337-
if (this.config.clusters <= 1 && !cluster.isWorker) {
359+
if (this.config.clusters <= 1 && cluster.isPrimary) {
338360
const totalCollectedPoints = accountStats.reduce((sum, s) => sum + s.collectedPoints, 0)
339361
const totalInitialPoints = accountStats.reduce((sum, s) => sum + s.initialPoints, 0)
340362
const totalFinalPoints = accountStats.reduce((sum, s) => sum + s.finalPoints, 0)
@@ -348,7 +370,7 @@ export class MicrosoftRewardsBot {
348370
)
349371

350372
await flushAllWebhooks()
351-
process.exit()
373+
process.exit(0)
352374
}
353375

354376
return accountStats

0 commit comments

Comments
 (0)