Skip to content

Login hook hangs login until max_execution_time when modules.processwire.com doesn't respond #38

Description

@adrianbj

When modules.processwire.com is unresponsive, the login hook in ProcessWireUpgradeCheck hangs the login request until PHP's max_execution_time kills it. The superuser's login succeeds, but the fatal error hits before the regenerated session is written, so they land back on the login page with no session. Every retry fails the same way, which locks the superuser out of the admin.

Environment: ProcessWireUpgradeCheck 9, ProcessWire 3.0.273, PHP 8.5, TFA enabled, default useLoginHook = 1.

What happens

  1. On superuser login, loginHook() calls getModuleVersions(), which requests $config->moduleServiceURL (https://modules.processwire.com/export-json/...).
  2. Right now that host completes the TLS handshake and then never sends a response:
    $ curl -s -o /dev/null -w "code=%{http_code} tls=%{time_appconnect}s total=%{time_total}s\n" --max-time 12 "https://modules.processwire.com/export-json/?apikey=pw300&limit=1"
    code=000 tls=0.047770s total=12.003673s
    
  3. WireHttp's fopen and curl attempts both honour the module's 4.5s timeout and fail. WireHttp then falls back to sendSocket(). The while(!feof($fs)) { fgets(...) } loop there (WireHttp.php ~line 1002) has no read timeout ($timeout only applies to fsockopen()'s connect), so it blocks until:
    Fatal error: Maximum execution time of 30+2 seconds exceeded (terminated) in wire/core/Tools/WireHttp/WireHttp.php on line 1002
    
  4. The 12-hour cache is only saved at the end of loginHook(), so it is never populated and every subsequent login repeats the hang.

The GitHub requests (branches, raw ProcessWire.php) are fine; only the modules directory request hangs.

Workaround: set useLoginHook to 0, e.g.

$c = $modules->getConfig('ProcessWireUpgradeCheck');
$c['useLoginHook'] = 0;
$modules->saveConfig('ProcessWireUpgradeCheck', $c);

Possible fixes

  • In this module, pass ['use' => ['curl', 'fopen']] to the WireHttp calls so a timeout doesn't fall through to the socket method. Alternatively, run the check somewhere other than the login request, or save a short negative cache entry when a request fails, so one outage doesn't hit every login.
  • In the core, sendSocket() could call stream_set_timeout($fs, $timeout) and stop when stream_get_meta_data($fs)['timed_out'] is set. I can open that in processwire-issues if useful.

A smaller thing noticed while reading loginHook():

if(!empty($cacheData) && is_string($cacheData)) $cache = json_decode($cacheData, true);

This assigns to $cache (the WireCache instance) rather than $cacheData. It looks like it should be $cacheData = json_decode(...).

Activity

  1. added a commit that references this issue on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions