Cloud Functions Execution Environment

Cloud Functions run in a fully-managed, serverless environment where Google handles infrastructure, operating systems, and runtime environments completely on your behalf. Each Cloud Function runs in its own isolated secure execution context, scales automatically, and has a lifecycle independent from other functions.

What does this mean for your Cloud Functions design and implementation? This document explains.

Supported Runtime & Runtime Updates

Cloud Functions offers Node.js runtime, currently in version v6.14.0. Where possible, we will follow the "LTS" release cycle of Node and will update minor and patch versions of the runtime as soon as or shortly after they become available.

Base Image

Cloud Functions uses a Debian-based execution environment and includes contents of the gcr.io/google-appengine/nodejs Docker image, with the Node.js runtime installed in the version specified above:

FROM gcr.io/google-appengine/nodejs
RUN install_node v6.14.0

To see what is included in the image, you can check its GitHub project, or pull and inspect the image itself. Updates to the language runtime (Node.js) are generally done automatically (unless otherwise notified), and include any changes in the definition of the base image.

Concurrency

Cloud Functions may start multiple function instances to scale your function up to meet the current load. These instances run in parallel, which results in having more than one parallel function execution.

However, each function instance handles only one concurrent request at a time. This means while your code is processing one request, there is no possibility of a second request being routed to the same function instance, and the original request can use the full amount of resources (CPU and memory) that you requested.

Because concurrent requests are processed by different function instances, they do not share variables or local memory. This is discussed in detail later in this document.

Stateless Functions

Cloud Functions implements the serverless paradigm, in which you just run your code without worrying about the underlying infrastructure, such as servers or virtual machines. To allow Google to automatically manage and scale the functions, they must be stateless—one function invocation should not rely on in-memory state set by a previous invocation. However, the existing state can often be reused as a performance optimization; see the recommendation in Tips and Tricks for details.

For example, the counter value returned by the following function does not correspond to the total function invocation count because invocations may be handled by different function instances, which don’t share global variables, memory, file systems, or other state:

Node.js

// Global variable, but only shared within function instance.
let count = 0;

/**
 * HTTP Cloud Function that counts how many times
 * it is executed within a specific instance.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.executionCount = (req, res) => {
  count++;

  // Note: the total function invocation count across
  // all instances may not be equal to this value!
  res.send(`Instance execution count: ${count}`);
};

If you need to share state across function invocations, your function should use a service such as Cloud Datastore, Cloud Firestore or Cloud Storage to persist data. This page contains a complete list of available storage options.

Cold Starts

A new function instance is started in two cases:

  • When you deploy your function.

  • When a new function instance is automatically created to scale up to the load, or occasionally to replace an existing instance.

Starting a new function instance involves loading the runtime (Node.js) and your code. Requests that include function instance startup (cold starts) may be slower than requests hitting existing function instances. If your function receives steady load, however, then the number of cold starts is typically negligible unless your function frequently crashes and requires restarting of the function environment. See Errors to learn how to handle errors properly and avoid cold starts.

Function Instance Lifespan

The environment running a function instance is typically resilient and reused by subsequent function invocations, unless the number of instances is being scaled down (due to lack of ongoing traffic), or your function crashes. This means that when one function execution ends, another function invocation may be handled by the same function instance. Therefore, it is recommended to cache state across invocations in global scope where possible. Your function should be still prepared to work without this cache available as there is no guarantee that the next invocation will reach the same function instance (see Stateless Functions).

Function Scope vs. Global Scope

A single function invocation results in executing only the body of the function declared as the entry point. The global scope in the function file, which is expected to contain the function definition, is executed on every cold start, but not if the instance has already been initialized.

Node.js

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP Cloud Function that declares a variable.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

You can assume that the global scope has been executed exactly once before your function code is invoked in a new function instance (and on every subsequent creation of a new function instance). However, you should not depend on the total number of or timing of global scope executions as they depend on the autoscaling managed by Google.

Function Execution Timeline

A function has access to the resources requested (CPU and memory) only for the duration of function execution. Code run outside of the execution periods is not guaranteed to execute, and it may be stopped at any time. Therefore, you should always signal the end of your function execution correctly (see HTTP Functions and Background Functions for guidance), and avoid running any code beyond it.

For example, the code executed after sending the HTTP response could be interrupted at any time:

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to early HTTP response
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterResponse = (req, res) => {
  res.end();

  // This statement may not execute
  console.log('Function complete!');
};

Execution Guarantees

Your functions are typically invoked once for each incoming event. However, Cloud Functions does not guarantee a single invocation in all cases because of differences in error scenarios.

The maximum or minimum number of times your function is going to be invoked on a single event depends on the type of your function:

  • HTTP functions are invoked at most once. This is because of the synchronous nature of HTTP calls, and it means that any error on handling function invocation will be returned without retrying. The caller of an HTTP function is expected to handle the errors and retry if needed.

  • Background functions are invoked at least once. This is because of asynchronous nature of handling events, in which there is no caller that waits for the response and could retry on error. An emitted event invokes the function with potential retries on failure (if requested on function deployment) and sporadic duplicate invocations for other reasons (even if retries on failure were not requested).

To make sure that your function behaves correctly on retried execution attempts, you should make it idempotent by implementing it so that an event results in the desired results (and side effects) even if it is delivered multiple times. In the case of HTTP functions, this also means returning the desired value even if the caller retries calls to the HTTP function endpoint. See Retrying Background Functions for more information on how to make your function idempotent.

Errors

The recommended way for a function to signal an error depends on the function type:

  • HTTP functions should return appropriate HTTP status codes which denote an error. See HTTP Functions for examples.

  • Background functions should return an error object through the callback parameter (if using the callback syntax), or a rejected promise (if using the promise syntax). See Background Functions for examples.

If an error is returned the recommended way, then the function instance that returned the error is labelled as behaving normally and can serve future requests if need be.

If your code or any other code you call throws an uncaught exception or crashes the current process, then the function instance may be restarted before handling the next invocation. This may lead to more cold starts, resulting in higher latency, and thus is discouraged.

See Reporting Errors for more discussion of how to report errors in Cloud Functions.

Timeout

Function execution time is limited by the timeout value, specified at function deployment time. It defaults to 1 minute, and can be extended up to 9 minutes. When function execution exceeds the timeout, an error status is immediately returned. Any remaining code that is executing may be terminated.

For example, the snippet below includes code that is scheduled to execute 2 minutes after function execution starts. If the timeout happens to be set to 1 minute, this code may never execute:

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to function execution timeout
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterTimeout = (req, res) => {
  setTimeout(() => {
    // May not execute if function's timeout is <2 minutes
    console.log('Function running...');
    res.end();
  }, 120000); // 2 minute delay
};

File System

The function execution environment contains a runnable function file, plus files and directories included in the deployed function package such as local dependencies. These files are available in a read-only directory, which can be determined based on the function file location. Note that the function’s directory may be different than the current working directory.

The following example lists files located in the function directory:

Node.js

const fs = require('fs');

/**
 * HTTP Cloud Function that lists files in the function directory
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.listFiles = (req, res) => {
  fs.readdir(__dirname, (err, files) => {
    if (err) {
      console.error(err);
      res.sendStatus(500);
    } else {
      console.log('Files', files);
      res.sendStatus(200);
    }
  });
};

You can also load code from other files deployed with the function. For example, the following code loads a script from a subdirectory of the function directory:

Node.js

const path = require('path');
const loadedModule = require(path.join(__dirname, 'loadable.js'));

/**
 * HTTP Cloud Function that runs a function loaded from another Node.js file
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.runLoadedModule = (req, res) => {
  console.log(`Loaded function from file ${loadedModule.getFileName()}`);
  res.end();
};

The only writeable part of the file system is the /tmp directory (as defined by os.tmpdir()), which you can use to store temporary files in a function instance. This is a local disk mount point known as a "tmpfs" volume in which data written to the volume is stored in memory. Note that it will consume memory resources provisioned for the function.

The rest of the file system is read-only and accessible to the function.

Network

Your function can access the public internet using standard libraries offered by the runtime or third-party providers. For example, you can call an HTTP endpoint this way using the request module:

Node.js

const request = require('request');

/**
 * HTTP Cloud Function that makes an HTTP request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.makeRequest = (req, res) => {
  // The URL to send the request to
  const url = 'https://example.com';

  request(url, (err, response) => {
    if (!err && response.statusCode === 200) {
      res.sendStatus(200);
    } else {
      res.sendStatus(500);
    }
  });
};

Try to reuse network connections across function invocations, as described in Optimizing Networking. However, note that a connection that remains unused for 2 minutes may be closed by the system, and further attempts to use a closed connection will result in a "connection reset" error. Your code should either use a library that handles closed connections well, or handle them explicitly if using low-level networking constructs.

Multiple Functions

Every deployed function is isolated from all other functions—even those deployed from the same source file. In particular, they don’t share memory, global variables, file systems, or other state.

To share data across deployed functions, you can use storage services like Cloud Datastore, Cloud Firestore, or Cloud Storage. Alternatively, you can invoke one function from another, using their appropriate triggers (for example, make an HTTP request to the endpoint of an HTTP function or publish a message to Cloud Pub/Sub topic to trigger a Cloud Pub/Sub function).

Send feedback about...

Cloud Functions Documentation