Function errors
Timeouts, memory exhaustion, cold starts and unhandled exceptions in a live Function, and how to tell them apart from a log.
A Function that is already serving fails in four ways that look alike from outside: it ran too long, it ran out of memory, it was not ready, or your code threw. The response status and the runtime log identify which failure occurred.
Timeouts
max_duration bounds one invocation, meaning one HTTP request, one response stream or one WebSocket
connection. The default is 30 seconds and the ceiling is 8 hours.
The deadline covers the whole response, including the body, not the time to the first byte. Where it lands decides what the client sees:
- Nothing sent yet. The invocation is aborted and the edge returns
504on a branded page carrying the error codeEDGE_FUNCTION_TIMEOUT. - Headers already sent. The status line is committed and cannot be taken back, so the body stream is cut instead. The client gets a truncated response with a success status and no error page.
Raise max_duration on the Function's config entry when the work genuinely takes that long. When it
does not, the runtime log locates the stall: the last line your code logged is the statement before
the one that hung.
Two cut-offs that are not your timeout
A response the CDN has held open for 60 seconds is closed regardless of max_duration, which is what usually severs
long server-sent-events streams; see How a request is served. A WebSocket
ends at whichever comes first out of max_duration and 4 hours, and separately after 15 minutes with no bytes in
either direction, as described in WebSockets.
Memory exhaustion
A Function gets the memory you declared, floored at 256 MB and rounded up to an even number, and one vCPU. That memory belongs to the execution environment and is shared by every request it is serving at that moment, up to 32 at once, so concurrency and memory are not independent settings.
There is no platform error for exceeding it. The kernel kills the process running your code, which produces two symptoms together:
- Requests in flight fail. The client sees
502. AGETorHEADis retried once against another environment, and no other method is ever replayed, because the platform cannot know whether your code already committed something. - The process is restarted automatically, with a backoff. Requests that arrive during the gap fail
the same way, so a Function that dies on start looks like an intermittent
502rather than a crash.
In the runtime log you either find the interpreter's own message, such as FATAL ERROR: ... JavaScript heap out of memory, arriving on standard error without a request attached, or nothing at all when
the kill was immediate.
Raise memory on that Function's entry in gigadrive.yaml. Your plan caps how much you can ask for
and the check runs at deploy time, not at request time; Memory and CPU
has the numbers.
Cold start symptoms
Publishing a deployment starts nothing. The first request to a Function prepares its image and starts the environment, and a request that cannot be served while that happens is refused rather than queued indefinitely.
| Status | Error code | What it means |
|---|---|---|
503 | EDGE_FUNCTION_AT_CAPACITY | Every warm environment was busy and another could not be added in time |
503 | EDGE_FUNCTION_RESUMING | The environment was finishing an idle transition |
503 | EDGE_FUNCTION_STARTING | A sticky-session request whose bound environment is still starting |
503 | EDGE_FUNCTION_START_FAILED | The start itself failed. Gigadrive is notified |
503 | EDGE_FUNCTION_NOT_READY | The Function's image could not be prepared |
503 | EDGE_ORGANIZATION_COMPUTE_LIMIT | The organization is at the active compute its plan includes |
The first three carry Retry-After: 2 and clear on their own. The last one needs an administrator:
free compute in the console, change plan, or ask support to raise the limit.
A 429 with EDGE_FUNCTION_CONCURRENCY_LIMIT is not a cold start. It means the Function is already
processing as many simultaneous requests as the plan allows, covered in
Concurrency.
What you can change here is the work your code does before it can answer: everything at module scope runs on every start. Pro and Enterprise plans can also keep an application's production Functions resident, described in The compute model. There are no warmers to configure.
Unhandled exceptions
What an uncaught throw does depends on the shape of your application, and the difference matters because only one of the two shapes survives it.
A handler-style Function, exporting fetch or a request handler, is wrapped by the runtime. A
throw is caught and answered with 500 and a JSON body of the form {"errorMessage": "..."}. The
process keeps running and the next request is served normally. Nothing is written to your runtime log
unless your own code logs it, so a 500 carrying that body with no matching log line is an uncaught
throw, not a platform fault.
A server-style Function, an Express, NestJS or raw node:http server, keeps its own error
handling and decides its own status. What it does not survive is a throw outside a request or an
unhandled promise rejection: Node ends the process, and from there the symptoms are identical to
running out of memory, 502 for whatever was in flight followed by a restart.
Catch at the top of your handler and log the error yourself. If you want crashes recorded rather than
inferred, add process.on('unhandledRejection') and process.on('uncaughtException') handlers that
log before exiting.
Reading a runtime log
Runtime logs live in the console under the environment's Logs page, in one feed with request logs.
Filter Activity by Requests or Runtime output, filter Stream by stdout or stderr, pick a time
range, and search.
Every line your code writes to standard output or standard error becomes one entry. Calls to
console.* made while an invocation is in flight are tagged with that request, which is what fills
the Runtime Output panel when you open a request. Anything written outside a request, or written
straight to the stream rather than through console, arrives uncorrelated, which is where crash
output and startup banners land.
That gives four signatures:
| What you see | What happened |
|---|---|
500 with {"errorMessage": ...}, no log line for it | A handler threw and the runtime answered for it |
502, and the request's output stops part-way | The process died mid-request |
504, and the last line is where the work stalled | The invocation ran past max_duration |
A branded 429 or 503, no runtime output at all | Your code never ran. The edge refused the request |
To pull the same picture outside the console, list the request logs filtered to server errors. The
credential needs the network:requests:read scope.
import { GigadriveClient } from '@gigadrive/sdk';
const client = new GigadriveClient({
clientId: process.env.GIGADRIVE_CLIENT_ID,
clientSecret: process.env.GIGADRIVE_CLIENT_SECRET,
});
const { items } = await client.applications.requests.list('0197b2f1-2f4a-7a0b-8a2d-111111111111', {
statusFamily: 5,
limit: 100,
});
for (const entry of items) {
console.log(
entry.startedAt,
entry.response.status,
entry.request.method,
entry.request.path,
`${String(entry.metrics.durationMs)}ms`
);
}Request logs documents the rest of the filters, and Runtime logs covers the feed itself.
