Many Linode API calls finish on the server after the HTTP response returns. Creating an instance, booting it, resizing a volume, and similar work is asynchronous.
linodego helps you wait for that work in two ways:
- Status polling — keep checking a resource until its status looks right (for example, an instance is
running). - Event polling — watch account events until a specific action finishes (for example,
linode_bootreachesfinished).
Every wait helper uses the client's poll interval and stops when:
- the condition is met
- an error is returned
- the
context.Contextis canceled or times out
| What you want | Use this |
|---|---|
| A resource reached a known status | A WaitFor*Status helper |
| A specific action finished on an entity | NewEventPoller + WaitForFinished |
| A create finished, but you did not know the ID yet | NewEventPollerWithoutEntity |
| An action on a nested resource (for example, a disk on an instance) | NewEventPollerWithSecondary |
| No in-progress events left on a resource | WaitForResourceFree |
| A timestamp-based event wait (lower-level alternative) | WaitForEventFinished |
Rule of thumb
- Use status waits when you care whether the resource looks ready.
- Use event waits when you care whether a particular action completed.
- Prefer
EventPolleroverWaitForEventFinishedwhen you can create the poller before the mutating API call.
By default, the client polls every 3 seconds (APISecondsPerPoll).
client.SetPollDelay(5 * time.Second)
delay := client.GetPollDelay()SetPollDelay controls how often wait helpers and event pollers check for progress.
By default, request retries also start with a 3 second minimum wait (SetRetryWaitTime), matching the poll delay. Those are separate settings: changing the poll delay does not automatically change retry timing.
Shorter poll delays notice completion sooner but create more API traffic. Longer delays are quieter but slower.
Always pass a deadline-aware context into the wait call itself (WaitForFinished, WaitForInstanceStatus, and so on). Without a deadline, a wait can block forever if the expected status or event never appears.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
instance, err := client.WaitForInstanceStatus(ctx, instanceID, linodego.InstanceRunning)
if err != nil {
log.Fatal(err)
}When the context expires, wait helpers return an error that includes ctx.Err().
For NewEventPoller and NewEventPollerWithSecondary, the create-poller call also takes a context because those helpers list existing events first. For NewEventPollerWithoutEntity, only the later wait call needs the deadline context.
Status helpers repeatedly fetch a resource and return once its status matches what you asked for.
| Method | Waits for |
|---|---|
WaitForInstanceStatus |
Instance status |
WaitForInstanceDiskStatus |
Instance disk status |
WaitForVolumeStatus |
Volume status |
WaitForVolumeLinodeID |
Volume attach or detach (LinodeID) |
WaitForVolumeIOReadyStatus |
Volume IOReady |
WaitForSnapshotStatus |
Instance snapshot status |
WaitForImageStatus |
Image status |
WaitForImageRegionStatus |
Image replica status in a region |
WaitForLKEClusterStatus |
LKE cluster status |
WaitForLKEClusterConditions |
Custom LKE conditions |
WaitForDatabaseStatus |
Managed database status |
WaitForAlertDefinitionStatus |
Monitor alert definition status |
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
instance, err := client.CreateInstance(ctx, linodego.InstanceCreateOptions{
Region: "us-east",
Type: "g6-nanode-1",
Label: "polling-example",
Image: "linode/ubuntu22.04",
RootPass: "replace-with-a-secure-password",
})
if err != nil {
log.Fatal(err)
}
instance, err = client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceRunning)
if err != nil {
log.Fatal(err)
}
fmt.Printf("instance %d is %s\n", instance.ID, instance.Status)volume, err := client.WaitForVolumeStatus(ctx, volumeID, linodego.VolumeActive)
if err != nil {
log.Fatal(err)
}// Wait until the volume is attached to this instance.
volume, err := client.WaitForVolumeLinodeID(ctx, volumeID, &instanceID)
if err != nil {
log.Fatal(err)
}
// Wait until the volume is detached.
volume, err = client.WaitForVolumeLinodeID(ctx, volumeID, nil)
if err != nil {
log.Fatal(err)
}Account events describe actions on entities. EventPoller watches for one entity and one action.
Create the poller before you trigger the operation so the helper can snapshot existing events (record a baseline of event IDs to ignore). That way old events are skipped, and you only wait for the new one.
This is the best option when:
- you care about a specific action completing
- the same entity may have several similar events over time
// 1. Create the poller first so current events are recorded and ignored.
poller, err := client.NewEventPoller(
ctx,
instance.ID,
linodego.EntityLinode,
linodego.ActionLinodeBoot,
)
if err != nil {
log.Fatal(err)
}
// 2. Trigger the operation.
if err := client.BootInstance(ctx, instance.ID, linodego.InstanceBootOptions{}); err != nil {
log.Fatal(err)
}
// 3. Wait for the matching event to finish.
event, err := poller.WaitForFinished(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("boot event %d finished with status %s\n", event.ID, event.Status)What WaitForFinished does:
- waits for the next unseen matching event
- polls that event until its status is
finished - returns
niland an error if the event status becomesfailed
If you need the failed event object itself, use WaitForEventFinished instead. That helper returns both the event and an error on failure.
If you only need the next matching event, and not necessarily a finished one, call WaitForLatestUnknownEvent:
event, err := poller.WaitForLatestUnknownEvent(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("observed event %d with status %s\n", event.ID, event.Status)When you create a resource, you usually do not know its ID until the create call returns. Use NewEventPollerWithoutEntity, set EntityID right after create, then wait.
Unlike NewEventPoller and NewEventPollerWithSecondary, this helper does not snapshot existing events up front. previousEvents starts empty. You can create the poller at any time, but you must set EntityID before calling WaitForFinished.
poller, err := client.NewEventPollerWithoutEntity(
linodego.EntityLinode,
linodego.ActionLinodeCreate,
)
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateInstance(ctx, linodego.InstanceCreateOptions{
Region: "us-east",
Type: "g6-nanode-1",
Label: "create-poll-example",
Booted: linodego.Pointer(false),
})
if err != nil {
log.Fatal(err)
}
// Set this before waiting. Even if create finished quickly, the poller can
// still match the create event because previousEvents starts empty.
poller.EntityID = instance.ID
event, err := poller.WaitForFinished(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("create event %d finished\n", event.ID)Some events have both a primary entity and a secondary entity. Deleting a disk on an instance is a common example: the instance is primary, and the disk is secondary.
NewEventPollerWithSecondary takes the secondary ID as an int, which fits nested resources such as disks.
poller, err := client.NewEventPollerWithSecondary(
ctx,
instance.ID, // primary entity
linodego.EntityLinode,
disk.ID, // secondary entity
linodego.ActionDiskDelete,
)
if err != nil {
log.Fatal(err)
}
if err := client.DeleteInstanceDisk(ctx, instance.ID, disk.ID); err != nil {
log.Fatal(err)
}
event, err := poller.WaitForFinished(ctx)
if err != nil {
log.Fatal(err)
}WaitForEventFinished is a lower-level helper. It finds events matching an entity and action that were created at or after a given timestamp, then waits until one reaches finished status.
event, err := client.WaitForEventFinished(
ctx,
instance.ID,
linodego.EntityLinode,
linodego.ActionLinodeCreate,
*instance.Created,
)
if err != nil {
// On failure, event may still be non-nil.
log.Fatal(err)
}Notes:
- Prefer
EventPollerwhen you can create the poller before the mutating call. It avoids timestamp edge cases and ignores events that already exist. - If the matched event fails, this helper returns both the event and an error.
- Entity filtering is optimized for disk, database, linode, domain, and nodebalancer entities. Other entity types may be less precise.
Use this when you want a resource to settle before starting another long-running operation. It waits until the entity has no events in started or scheduled status.
if err := client.WaitForResourceFree(ctx, linodego.EntityLinode, instance.ID); err != nil {
log.Fatal(err)
}- Put the deadline on the wait call. Status waits,
WaitForFinished, andWaitForEventFinishedall need a context that can expire. - Create snapshotting pollers before the mutating call.
NewEventPollerandNewEventPollerWithSecondarysnapshot existing events first.NewEventPollerWithoutEntitydoes not. - Pick the wait that matches your goal. Status waits answer "is it ready?" Event waits answer "did this action finish?"
- Tune the poll delay carefully. Faster polling is more responsive; slower polling is gentler on the API.
- Handle failed events.
EventPoller.WaitForFinishedreturnsnil, erroron failure.WaitForEventFinishedreturns the failed event along with an error.
- Implementation: waitfor.go
- Event, entity, and action constants: account_events.go
- Common event statuses:
EventScheduled,EventStarted,EventFinished,EventFailed,EventNotification,EventCanceled