# Handle app suspension





<Callout type="warn">
  Getting suspension wrong is silent: the OS gives the app no warning before it force-terminates it.
  A backgrounded app that leaves an HTTP server listening, a TCP socket open, or a live timer running
  will be killed—usually within a few seconds on iOS and Android.
</Callout>

Mobile operating systems suspend apps when they move to the background. Bare models this explicitly
through its [lifecycle](/bare/reference/bare/runtime#lifecycle):

1. the host calls `Bare.suspend()`, which emits a `suspend` event so your code can stop outstanding work,
2. then the loop drains and emits `idle`,
3. and finally blocks—keeping the process alive but quiet.
4. `Bare.resume()` brings it back.

The problem is step 2. The loop goes `idle` only when it has **no remaining referenced handles**: no open sockets, no listening servers, no active timers, no pending file operations.
If any referenced handle survives the `suspend` event, the loop never emits `idle`, the process never fully suspends, and the OS terminates it without warning when its patience runs out.

<Callout type="info">
  For the state machine behind this, see [Inside Bare](/bare/explanation/bare-runtime#the-lifecycle).
</Callout>

## The two patterns [#the-two-patterns]

Every handle you manage falls into one of two categories.

### Pattern A — `unref()` for handles that survive suspension [#pattern-a--unref-for-handles-that-survive-suspension]

Some handles must stay open across a suspend/resume cycle—for example, the IPC channel between a
[bare-kit worklet](/bare/reference/bare/bare-kit) and its host. Calling [`.unref()`](/bare/reference/bare/modules/bare-ipc#unref-this) removes the handle
from the loop's reference count without closing it, so the loop can go idle while the handle stays
alive. Call [`.ref()`](/bare/reference/bare/modules/bare-ipc#ref-this) on `resume` to recount it.

```js skip="snippet-illustration"
const IPC = require('bare-ipc')
const [portA] = IPC.open()
const ipc = portA.connect()

Bare.on('suspend', () => ipc.unref())
Bare.on('resume', () => ipc.ref())
```

### Pattern B—close on suspend, recreate on resume [#pattern-bclose-on-suspend-recreate-on-resume]

Most active I/O should not survive suspension. Stop it on `suspend` and start it again on
`resume`. The OS connection timeout is shorter than you might expect—don't rely on connections
surviving while the process is idle.

## Examples [#examples]

### Stop a `bare-tcp` server [#stop-a-bare-tcp-server]

`server.close()` stops accepting new connections, but existing connections are still referenced.
Destroy them first, or the loop will not go idle.

```js skip="snippet-illustration"
const net = require('bare-tcp')

let server = null

function startServer () {
  server = net.createServer((socket) => {
    socket.on('data', (data) => socket.write(data)) // echo
  })
  server.listen(3000)
}

startServer()

Bare.on('suspend', () => {
  for (const socket of server.connections) socket.destroy()
  server.close()
  server = null
})

Bare.on('resume', () => startServer())
```

<Callout type="info">
  See [`bare-tcp`](/bare/reference/bare/modules/bare-tcp) for the full server and socket API.
</Callout>

### Suspend Hyperswarm [#suspend-hyperswarm]

Each `connection` event gives you a live socket stream that Hyperswarm tracks internally. Rather than
tearing the swarm down, call [`swarm.suspend()`](/p2p/reference/building-blocks/hyperswarm#await-swarmsuspend-log---)
to pause its connections, listening server, and discovery, and
[`swarm.resume()`](/p2p/reference/building-blocks/hyperswarm#await-swarmresume-log---) to bring them
back—the swarm instance and its joined topics survive the cycle.

```js skip="snippet-illustration"
const Hyperswarm = require('hyperswarm')
const Corestore = require('corestore')

const store = new Corestore('./data')
const topic = Buffer.alloc(32) // your 32-byte topic

const swarm = new Hyperswarm()
swarm.on('connection', (conn) => store.replicate(conn))
swarm.join(topic)

Bare.on('suspend', async () => {
  await swarm.suspend() // pause connections, server, and discovery
  await store.suspend() // flush buffered writes to disk
})

Bare.on('resume', async () => {
  await store.resume()
  await swarm.resume() // reconnect and rejoin discovery
})
```

Suspending the swarm stops the replication streams and DHT socket—the handles it holds in the
loop—while keeping the swarm and its joined topics, so `resume` reconnects without rejoining. The
`store` does not keep the loop referenced while idle, but it may hold buffered writes that have not
yet reached disk. Call [`store.suspend()`](/p2p/reference/helpers/corestore#await-storesuspendoptions)
to flush them before the process suspends, and [`store.resume()`](/p2p/reference/helpers/corestore#await-storeresume)
on the way back—if the OS force-terminates the app before a flush, recent writes can be lost.

<Callout type="info">
  See [Hyperswarm](/p2p/reference/building-blocks/hyperswarm) and [Corestore](/p2p/reference/helpers/corestore).
</Callout>

### Unref the `bare-kit` IPC channel [#unref-the-bare-kit-ipc-channel]

The IPC channel between a worklet and its native host must survive suspension so the host can
still send messages (for example, a `resume` instruction). Use [`.unref()`](/bare/reference/bare/modules/bare-ipc#unref-this) / [`.ref()`](/bare/reference/bare/modules/bare-ipc#ref-this) (Pattern A):

```js skip="snippet-illustration"
// worklet entry — runs inside Bare
const { IPC } = BareKit

Bare.on('suspend', () => IPC.unref())
Bare.on('resume', () => IPC.ref())

IPC.on('data', (data) => {
  IPC.write(Buffer.from(`echo: ${data}`))
})
```

<Callout type="info">
  See [`bare-ipc`](/bare/reference/bare/modules/bare-ipc) for the [`.ref()`](/bare/reference/bare/modules/bare-ipc#ref-this) / [`.unref()`](/bare/reference/bare/modules/bare-ipc#unref-this) API, and
  [`bare-kit`](/bare/reference/bare/bare-kit) for the host-side worklet API.
</Callout>

### Clear timers [#clear-timers]

Active [`setInterval`](/bare/reference/bare/modules/bare-timers#setinterval) and
[`setTimeout`](/bare/reference/bare/modules/bare-timers#settimeout) callbacks hold a
reference in the event loop just like open sockets. Clear them on `suspend` and restart on `resume`.

```js skip="snippet-illustration"
let heartbeat = null

function startHeartbeat () {
  heartbeat = setInterval(() => sync(), 30_000)
}

startHeartbeat()

Bare.on('suspend', () => {
  clearInterval(heartbeat)
  heartbeat = null
})

Bare.on('resume', () => startHeartbeat())
```

<Callout type="info">
  See [`bare-timers`](/bare/reference/bare/modules/bare-timers) for the full timer API, including
  [`.ref()`/`.unref()`](/bare/reference/bare/modules/bare-timers#timeout)
  for a timer that must keep running across the cycle.
</Callout>

### How the host triggers suspension [#how-the-host-triggers-suspension]

On mobile, the embedder calls [`bare_suspend()`](/bare/reference/bare/runtime#lifecycle) and [`bare_resume()`](/bare/reference/bare/runtime#lifecycle) from the C API when the OS
fires its own app-lifecycle callbacks.

<Callout type="info">
  [`react-native-bare-kit`](https://github.com/holepunchto/react-native-bare-kit) subscribes to React Native
  [`AppState`](https://reactnative.dev/docs/appstate) changes and calls `worklet.suspend()` and
  `worklet.resume()` on your behalf. You do not need to wire this up yourself; adding your own
  listeners is redundant but harmless.
</Callout>

If you need explicit control—for example, to coordinate suspension with custom host
logic—you can drive the worklet directly:

```js skip="snippet-illustration"
// React Native host component
import { AppState } from 'react-native'
import { Worklet } from 'react-native-bare-kit'

const worklet = new Worklet()
worklet.start('/app.js', source)

AppState.addEventListener('change', (state) => {
  if (state === 'background') worklet.suspend()
  else if (state === 'active') worklet.resume()
})
```

Whether suspension is triggered by `react-native-bare-kit` or from your own host code, the JavaScript
inside the worklet responds to the resulting [`suspend`](/bare/reference/bare/runtime#events) and
[`resume`](/bare/reference/bare/runtime#events) events via `Bare.on('suspend')` and `Bare.on('resume')`.

<Callout type="info">
  See [Embed Bare in a React Native app](/bare/how-to/run-on-native/embed-bare-in-react-native) for the
  full worklet setup.
</Callout>

***

## See also [#see-also]

* [Inside Bare](/bare/explanation/bare-runtime#the-lifecycle)—why the lifecycle model exists and the state machine behind it.
* [Bare runtime API](/bare/reference/bare/runtime#lifecycle)—the full `suspend`, `wakeup`, `idle`, and `resume` event reference.
* [One core, many platforms](/bare/explanation/bare-on-native)—embedding Bare in a native app.
* [Embed Bare in a React Native app](/bare/how-to/run-on-native/embed-bare-in-react-native)—the worklet and IPC setup this page builds on.
* [`bare-kit` reference](/bare/reference/bare/bare-kit)—worklet API and host IPC channel.
* [`bare-tcp`](/bare/reference/bare/modules/bare-tcp)—TCP server and socket.
* [`bare-ipc`](/bare/reference/bare/modules/bare-ipc)—IPC streams and `ref()` / `unref()`.
* [`bare-timers`](/bare/reference/bare/modules/bare-timers)—timer functions and their `ref()` / `unref()`.
