diff --git a/.github/workflows/test-examples.yml b/.github/workflows/test-examples.yml index d372ee3..8cb2fe4 100644 --- a/.github/workflows/test-examples.yml +++ b/.github/workflows/test-examples.yml @@ -223,6 +223,9 @@ jobs: - example: networking-py args: "--net -- /urllib_get.py" expect: "SUCCESS: urllib GET worked!" + - example: networking-py + args: "--net -- /urllib_get_no_timeout.py" + expect: "SUCCESS: urllib GET \\(no timeout\\) worked!" - example: networking-py args: "--port 8080 -- /echo_server_test.py" expect: "SUCCESS: bind\\+listen on port 8080 allowed" @@ -613,6 +616,9 @@ jobs: - example: networking-py args: "--net -- /urllib_get.py" expect: "SUCCESS: urllib GET worked!" + - example: networking-py + args: "--net -- /urllib_get_no_timeout.py" + expect: "SUCCESS: urllib GET \\(no timeout\\) worked!" - example: networking-py args: "--port 8080 -- /echo_server_test.py" expect: "SUCCESS: bind\\+listen on port 8080 allowed" @@ -753,16 +759,30 @@ jobs: } } + # Helper: run a command with a per-test timeout (seconds). + function Invoke-WithTimeout { + param([int]$Seconds, [string]$Exe, [string[]]$ArgList) + $outFile = Join-Path $env:RUNNER_TEMP 'hl-test-out.log' + $proc = Start-Process -FilePath $Exe -ArgumentList $ArgList ` + -NoNewWindow -RedirectStandardOutput $outFile -RedirectStandardError "$outFile.err" -PassThru + if (-not $proc.WaitForExit($Seconds * 1000)) { + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + Write-Host "FAIL: timed out after ${Seconds}s" + Get-Content $outFile -ErrorAction SilentlyContinue + exit 1 + } + $script:out = Get-Content $outFile -Raw -ErrorAction SilentlyContinue + $script:rc = $proc.ExitCode + } + switch ($driver) { 'multifn-test' { - $out = & multifn-test $kernel $cpio 2>&1 - $rc = $LASTEXITCODE + Invoke-WithTimeout -Seconds 60 -Exe 'multifn-test' -ArgList @($kernel, $cpio) } 'pydriver-run' { "print('hello from driver')" | Out-File -Encoding ascii tiny.py $tiny = (Resolve-Path "tiny.py").Path - $out = & pydriver-run $kernel $cpio $tiny 2>&1 - $rc = $LASTEXITCODE + Invoke-WithTimeout -Seconds 120 -Exe 'pydriver-run' -ArgList @($kernel, $cpio, $tiny) } default { $argList = @() @@ -773,16 +793,15 @@ jobs: if ($memory -ne '') { $memArgs = @('-m', $memory) } - $out = & hyperlight-unikraft -q @memArgs ` - $kernel --initrd $cpio @mountArgs @toolArgs @argList 2>&1 - $rc = $LASTEXITCODE + Invoke-WithTimeout -Seconds 120 -Exe 'hyperlight-unikraft' ` + -ArgList (@('-q') + $memArgs + @($kernel, '--initrd', $cpio) + $mountArgs + $toolArgs + $argList) } } - $outStr = ($out | Out-String) + if ($null -eq $out) { $out = '' } Write-Host "=== output (exit=$rc) ===" - Write-Host $outStr - if (-not ($outStr -match $expect)) { + Write-Host $out + if (-not ($out -match $expect)) { Write-Host "FAIL: did not match /$expect/" exit 1 } diff --git a/examples/networking-py/Dockerfile b/examples/networking-py/Dockerfile index c16ba5b..b550171 100644 --- a/examples/networking-py/Dockerfile +++ b/examples/networking-py/Dockerfile @@ -8,6 +8,7 @@ FROM ${BASE} AS rootfs COPY http_get.py /http_get.py COPY echo_server.py /echo_server.py COPY urllib_get.py /urllib_get.py +COPY urllib_get_no_timeout.py /urllib_get_no_timeout.py COPY https_test.py /https_test.py COPY echo_server_test.py /echo_server_test.py diff --git a/examples/networking-py/urllib_get_no_timeout.py b/examples/networking-py/urllib_get_no_timeout.py new file mode 100644 index 0000000..fe937a4 --- /dev/null +++ b/examples/networking-py/urllib_get_no_timeout.py @@ -0,0 +1,25 @@ +"""HTTP GET using urllib WITHOUT an explicit timeout. + +Same as urllib_get.py but omits the timeout= argument to urlopen(). +This exercises the code path used by mxc, where the Unikraft guest +kernel relies on the idle thread's halt_irq callback to poll sockets +via __hl_sleep rather than an explicit timeout-driven poll cycle. +""" +import urllib.request +import sys + +URL = "http://example.com/" + +print(f"Fetching {URL} (no timeout) ...") +try: + with urllib.request.urlopen(URL) as resp: + body = resp.read().decode("utf-8", errors="replace") + print(f"Status: {resp.status}") + print(f"Body length: {len(body)} bytes") + if "Example Domain" in body: + print("SUCCESS: urllib GET (no timeout) worked!") + else: + print("WARNING: unexpected body content") +except Exception as e: + print(f"FAILED: {e}") + sys.exit(1)