jobs

package
v0.107.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 8, 2023 License: Apache-2.0 Imports: 10 Imported by: 0

README

package jobs

jobs package provides utilities to background job management.

A Job, at its core, is nothing more than a synchronous function.

func(ctx context.Context) error {
	return nil
}

Working with synchronous functions removes the complexity of thinking about how to run your application in your main. Your components become more stateless and focus on the domain rather than the lifecycle management, such as implementing a graceful async shutdown. This less stateful approach can help to make testing also easier.

Short-lived Jobs with Repeat

If your Job is a short-lived interaction, which is meant to be executed continuously between intervals, then you can use the jobs.WithRepeat to implement a continuous execution that stops on a shutdown signal.

job := jobs.WithRepeat(schedule.Interval(time.Second), func(ctx context.Context) error {
	// I'm a short-lived job, and I prefer to be constantly executed,
	// Repeat will keep repeating to me every second until the shutdown is signalled.
	return nil
})

ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
defer cancel()

if err := job(ctx); err != nil {
	log.Println("ERROR", err.Error())
}
scheduling

In the schedule package, you can choose from various options on how would you like to schedule your job.

  • Schedule by time duration interval
schedule.Interval(time.Second) // schedule every second
  • Schedule on a Daily basis
schedule.Daily{Hour:12} // schedule every day at 12 o'clock
  • Schedule on a Monthly basis
// schedule every month at 12 o'clock on the third day
schedule.Monthly{Day: 3, Hour:12} 

Long-lived Jobs

If your job requires continuous work, you can use the received context as a parent context to get notified about a shutdown event. This allows simplicity in your code so you don't have to differentiate if you need to cancel operations because of a request cancellation or because of a shutdown event. You can still separate the two cancellation types by using background context.

func MyJob(signal context.Context) error {
	<-signal.Done() // work until shutdown signal
	return signal.Err() // returning the context error is not an issue.
}

Scheduled Jobs with Scheduler.WithSchedule

If you need cron-like background jobs with the guarantee that your background jobs are serialised across your application instances, and only one scheduled job can run at a time, then you may use jobs.Scheduler, which solves that for you.

m := schedule.Scheduler{
    LockerFactory: postgresql.NewLockerFactory[string](db),
    Repository:    postgresql.NewRepository[jobs.ScheduleState, string]{/* ... */},
}

job := m.WithSchedule("db maintenance", schedule.Interval(time.Hour*24*7), func(ctx context.Context) error {
    // this job is scheduled to run once at every seven days
    return nil
})

job := m.WithSchedule("db maintenance", schedule.Monthly{Day: 1}, func(ctx context.Context) error {
    // this job is scheduled to run once at every seven days
    return nil
})

Using components as Job with Graceful shutdown support

If your application components signal shutdown with a method interaction, like how http.Server do, then you can use jobs.WithShutdown to combine the entry-point method and the shutdown method into a single jobs.Job lambda expression. The graceful shutdown has a timeout, and the shutdown context will be cancelled afterwards.

srv := http.Server{
	Addr: "localhost:8080",
	Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusTeapot)
	}),
}

httpServerJob := jobs.WithShutdown(srv.ListenAndServe, srv.Shutdown)

Managing multiple Jobs with a Manager

To manage the execution of these Jobs, you can use the jobs.Manager. The Manager will run Jobs on their goroutine, and if any of them fails with an error, it will signal shutdown to the other Jobs. Jobs which finish without an error are not considered an issue, and won't trigger a shutdown request to the other Jobs.

This behaviour makes Jobs act as an atomic unit where you can be guaranteed that either everything works, or everything shuts down, and you can safely restart your application instance. The jobs.Manager also takes listens to the shutdown syscalls.

sm := jobs.Manager{
	Jobs: []jobs.Job{ // each Job will run on its goroutine.
		MyJob,
		httpServerJob,
	},
}

if err := sm.Run(context.Background()); err != nil {
	log.Println("ERROR", err.Error())
}

Using jobs.Manager is most suitable in the main function.

Documentation

Overview

Package jobs provides utilities to background job management to achieve simplicity.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, jobs ...Job) error
Example
package main

import (
	"context"
	jobspkg "github.com/adamluzsi/frameless/pkg/jobs"
	"log"
	"net/http"
)

func main() {
	simpleJob := func(signal context.Context) error {
		<-signal.Done() // work until shutdown signal
		return signal.Err()
	}

	srv := http.Server{
		Addr: "localhost:8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusTeapot)
		}),
	}

	httpServerJob := jobspkg.WithShutdown(srv.ListenAndServe, srv.Shutdown)

	if err := jobspkg.Run(context.Background(), simpleJob, httpServerJob); err != nil {
		log.Println("ERROR", err.Error())
	}
}
Output:

Types

type Job

type Job func(context.Context) error

Job is the basic unit of jobs package, that represents an executable work.

Job at its core, is nothing more than a synchronous function. Working with synchronous functions removes the complexity of thinking about how to run your application. Your components become more stateless and focus on the domain rather than the lifecycle management. This less stateful approach can help to make testing your Job also easier.

func ToJob added in v0.106.0

func ToJob[JFN genericJob](fn JFN) Job

func WithRepeat added in v0.104.0

func WithRepeat[JFN genericJob](interval internal.Interval, jfn JFN) Job

WithRepeat will keep repeating a given Job until shutdown is signaled. It is most suitable for Job(s) meant to be short-lived and executed continuously until the shutdown signal.

Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/jobs"
	"github.com/adamluzsi/frameless/pkg/jobs/schedule"
	"log"
	"time"
)

func main() {
	job := jobs.WithRepeat(schedule.Interval(time.Second), func(ctx context.Context) error {
		// I'm a short-lived job, and prefer to be constantly executed,
		// Repeat will keep repeating me every second until shutdown is signaled.
		return nil
	})

	ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
	defer cancel()

	if err := job(ctx); err != nil {
		log.Println("ERROR", err.Error())
	}
}
Output:

func WithShutdown added in v0.104.0

func WithShutdown[StartFn, StopFn genericJob](start StartFn, stop StopFn) Job

WithShutdown will combine the start and stop/shutdown function into a single Job function. It supports a graceful shutdown period; upon reaching the deadline, it will cancel the context passed to the shutdown function. WithShutdown makes it easy to use components with graceful shutdown support as a Job, such as the http.Server.

jobs.JobWithShutdown(srv.ListenAndServe, srv.Shutdown)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/jobs"
	"log"
	"net/http"
)

func main() {
	srv := http.Server{
		Addr: "localhost:8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusTeapot)
		}),
	}

	httpServerJob := jobs.WithShutdown(srv.ListenAndServe, srv.Shutdown)
	_ = httpServerJob

	ctx, cancel := context.WithCancel(context.Background())
	// listen to a cancellation signal and then call the cancel func
	// or use ShutdownManager.
	_ = cancel

	if err := httpServerJob(ctx); err != nil {
		log.Println("ERROR", err.Error())
	}
}
Output:

type Manager added in v0.104.0

type Manager struct {
	// Jobs is the list of background job that you wish to run concurrently.
	// They will act as a unit, if any of them fail with an error,
	// other jobs will be notified to shut down.
	Jobs []Job
	// Signals is an [OPTIONAL] parameter
	// where you can define what signals you want to consider as a shutdown signal.
	// If not defined, then the default signal values are INT, HUB and TERM.
	Signals []os.Signal
}

Manager helps to manage concurrent background Jobs in your main. Each Job will run in its own goroutine. If any of the Job encounters a failure, the other jobs will receive a cancellation signal.

Example
package main

import (
	"context"
	jobspkg "github.com/adamluzsi/frameless/pkg/jobs"
	"log"
	"net/http"
)

func main() {
	simpleJob := func(signal context.Context) error {
		<-signal.Done() // work until shutdown signal
		return signal.Err()
	}

	srv := http.Server{
		Addr: "localhost:8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusTeapot)
		}),
	}

	httpServerJob := jobspkg.WithShutdown(srv.ListenAndServe, srv.Shutdown)

	sm := jobspkg.Manager{
		Jobs: []jobspkg.Job{ // each Job will run on its own goroutine.
			simpleJob,
			httpServerJob,
		},
	}

	if err := sm.Run(context.Background()); err != nil {
		log.Println("ERROR", err.Error())
	}
}
Output:

func (Manager) Run added in v0.104.0

func (m Manager) Run(ctx context.Context) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL