TL;DR
-
Fixes #778: enabling SmartProxy while the app is already running never resumed quota-stalled downloads. Symptom: the proxy list refreshes fine (
[Smart Proxy] proxy list refreshed (N entries)) and then nothing happens — downloads stay frozen — while the only workaround was to fully relaunch with SmartProxy pre-enabled. This affected every proxy type, not just authenticated ones. -
Root cause: a Java Memory Model data race. The proxy-routing fields in
MainPanel(_use_smart_proxy,_proxy_manager,_use_proxy, the static-proxy host/port/user/pass) were plain non-volatilestatics with unsynchronized getters. They are read by long-lived download worker threads and written on the Swing EDT when you toggle Settings. When SmartProxy is enabled at startup the workers are created after the write (soThread.start()supplies the needed happens-before edge); when it is enabled at runtime the workers already exist, so with novolatilethere is no happens-before edge — an already-running worker can keep reading the stale value (false/null) forever, or the JIT can hoist the read out of its hot loop. Either way the worker never enters the proxy-routing branch, keeps hitting MEGA direct, and loops509 → backoff → 509with zero proxy activity in the log. The #758 "re-readgetProxy_manager()every iteration" comment assumed re-reading made runtime toggles visible; under the JMM it does not withoutvolatile. -
Fix: the proxy-routing statics and
_proxy_managerare nowvolatile— a one-word fix that restores correct visibility for already-running workers. The same race class was also fixed for the post-509 / post-finish external-command statics (_run_command*). -
Several related hardening fixes shipped alongside (see below): a stream-download NPE, a download-path NPE in
checkMegaDownloadUrl, thegetProxy()monitor that could freeze the UI, an unbounded per-worker exclusion list, and the manual Refresh-SmartProxy button now resumes stalled workers too.
What was wrong
ChunkDownloader / ChunkDownloaderMono re-read MainPanel.getProxy_manager() and check MainPanel.isUse_smart_proxy() on every loop iteration so that a runtime SmartProxy-enable is picked up. But those statics were not volatile, so a worker thread started before the toggle had no guarantee of ever observing the new values. The visible effect was exactly the bug report: refresh succeeds, the workers never switch to the proxy pool, downloads stay stalled. Relaunching "fixed" it only because the workers were then recreated after the value was already set.
Two more code paths could fail in the reporter's exact configuration (FORCE mode + a large, flaky public proxy pool):
-
StreamChunkDownloadercaptured the proxy manager once before its loop and never re-read it, and its routing branch did not null-guard it. A stream started with SmartProxy off kept the managernullfor life; the first 509 after a runtime enable threw an NPE atproxy_manager.getProxy(). -
MiscTools.checkMegaDownloadUrl(used on the download path byDownload.getMegaFileDownloadUrland by the streamer) indexedgetProxy()[0]unconditionally, and its FORCE-mode pick sits outside the method'stry, so an exhausted pool threw an uncaught NPE straight into the caller.
What changes
-
MainPanel—_use_smart_proxy,_proxy_manager,_use_proxy,_proxy_host,_proxy_port,_proxy_user,_proxy_pass, and_run_command/_run_command_path/_run_command_{dl,ul}_finish[_path]are nowvolatile. -
SmartMegaProxyManager— the JDK-wide proxyAuthenticatoris now installed in the constructor instead of only on the startup path, so a runtime-enable also authenticatesIP:PORT@b64user:b64passproxies.getProxy()no longer holds the manager monitor while it sleeps and refreshes: the selection moved to a synchronizedpickProxy()helper, and theThread.sleep(30 s) × 5+ blockingrefreshProxyList()now run without the lock (the old code serialized every worker behind the monitor for minutes on an exhausted pool, and the on-EDTrefreshSmartProxySettings()— also de-synchronized now, it never touched the pool — could freeze the UI). -
ChunkDownloader/ChunkDownloaderMono/MegaAPI/StreamChunkDownloader/MiscTools— the per-worker excluded-proxy list is now cleared when the pool is exhausted, instead of locking the worker into direct → 509 → exp-backoff forever (timeouts/429s only soft-exclude and were previously never cleared except on a successful chunk; hard failures stay banned byban_time). -
StreamChunkDownloader— re-reads the proxy manager every iteration and null-guards the routing branch (no more NPE / no-op on runtime enable). -
MiscTools.checkMegaDownloadUrl— all threegetProxy()results are null-guarded. -
Manual "Refresh SmartProxy" button — now also wakes stalled workers (breaks 509 exp-backoff and the auto-retry countdown), matching the behaviour of enabling SmartProxy in Settings; a freshly refreshed pool is exactly when stalled downloads should retry immediately.
Other
- Version bumped to 8.57 in
pom.xmlandMainPanel.VERSION. - No changes to the file-data transfer/decrypt path, the resume queue, the account-login flow, the MEGA error-popup machinery, the upload pipeline, or any i18n bundle.
- Notes left for a future pass (out of scope, low impact): uploads are not routed through SmartProxy; the pre-worker metadata/URL retry loop in
Download.run()is not interruptible by a runtime enable;ChunkDownloaderMonodoes not participate in the shared post-509 recheck window.