GithubHelp home page GithubHelp logo

geofmureithi / apalis Goto Github PK

View Code? Open in Web Editor NEW
429.0 6.0 36.0 1.56 MB

Simple, extensible multithreaded background job and message processing library for Rust

Home Page: https://crates.io/crates/apalis

License: MIT License

Lua 2.37% Rust 92.74% PLpgSQL 4.88%
background-jobs rust mysql postgres redis sqlite message-queue job-scheduler

apalis's Introduction

apalis

Simple, extensible multithreaded background job and messages processing library for Rust


Features

  • Simple and predictable job handling model.
  • Jobs handlers with a macro free API.
  • Take full advantage of the tower ecosystem of middleware, services, and utilities.
  • Runtime agnostic - Use tokio, smol etc.
  • Optional Web interface to help you manage your jobs.

apalis job processing is powered by tower::Service which means you have access to the tower middleware.

apalis has support for:

Source Crate Example
Cron Jobs
Redis
Sqlite
Postgres
MySQL
Amqp
From Scratch

Getting Started

To get started, just add to Cargo.toml

[dependencies]
apalis = { version = "0.5", features = ["redis"] } # Backends available: postgres, sqlite, mysql, amqp

Usage

use apalis::prelude::*;
use apalis::redis::RedisStorage;
use serde::{Deserialize, Serialize};
use anyhow::Result;

#[derive(Debug, Deserialize, Serialize)]
struct Email {
    to: String,
}

impl Job for Email {
    const NAME: &'static str = "apalis::Email";
}

/// A function that will be converted into a service.
async fn send_email(job: Email, data: Data<usize>) -> Result<(), Error> {
  /// execute job
  Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    std::env::set_var("RUST_LOG", "debug");
    env_logger::init();
    let redis_url = std::env::var("REDIS_URL").expect("Missing env variable REDIS_URL");
    let storage = RedisStorage::new(redis).await?;
    Monitor::new()
        .register_with_count(2, {
            WorkerBuilder::new(format!("email-worker"))
                .data(0usize)
                .with_storage(storage)
                .build_fn(send_email)
        })
        .run()
        .await
}

Then

//This can be in another part of the program or another application eg a http server
async fn produce_route_jobs(storage: &RedisStorage<Email>) -> Result<()> {
    let mut storage = storage.clone();
    storage
        .push(Email {
            to: "[email protected]".to_string(),
        })
        .await?;
}

Feature flags

  • tracing (enabled by default) โ€” Support Tracing ๐Ÿ‘€
  • redis โ€” Include redis storage
  • postgres โ€” Include Postgres storage
  • sqlite โ€” Include SQlite storage
  • mysql โ€” Include MySql storage
  • cron โ€” Include cron job processing
  • sentry โ€” Support for Sentry exception and performance monitoring
  • prometheus โ€” Support Prometheus metrics
  • retry โ€” Support direct retrying jobs
  • timeout โ€” Support timeouts on jobs
  • limit โ€” ๐Ÿ’ช Limit the amount of jobs
  • filter โ€” Support filtering jobs based on a predicate

Storage Comparison

Since we provide a few storage solutions, here is a table comparing them:

Feature Redis Sqlite Postgres Sled Mysql Mongo Cron
Scheduled jobs โœ“ โœ“ โœ“ x โœ“ x โœ“
Retry jobs โœ“ โœ“ โœ“ x โœ“ x โœ“
Persistence โœ“ โœ“ โœ“ x โœ“ x BYO
Rerun Dead jobs โœ“ โœ“ โœ“ x โœ“ x x

How apalis works

Here is a basic example of how the core parts integrate

sequenceDiagram
    participant App
    participant Worker
    participant Backend

    App->>+Backend: Add job to queue
    Backend-->>+Worker: Job data
    Worker->>+Backend: Update job status to 'Running'
    Worker->>+App: Started job
    loop job execution
        Worker-->>-App: Report job progress
    end
    Worker->>+Backend: Update job status to 'completed'
Loading

External examples

Projects using apalis

  • Ryot: A self hosted platform for tracking various facets of your life - media, fitness etc.
  • Summarizer: Podcast summarizer
  • Universal Inbox: Universal Inbox is a solution that centralizes all your notifications and tasks in one place to create a unique inbox.

Resources

Web UI

If you are running apalis Board, you can easily manage your jobs. See a working rest API example here

Thanks to

  • tower - Tower is a library of modular and reusable components for building robust networking clients and servers.
  • redis-rs - Redis library for rust
  • sqlx - The Rust SQL Toolkit

Roadmap

v 0.5

  • Refactor the crates structure
  • Mocking utilities
  • Support for SurrealDB and Mongo
  • Lock free for Postgres
  • Add more utility layers
  • Use extractors in job fn structure
  • Polish up documentation
  • Improve and standardize apalis Board
  • Benchmarks

v 0.4

  • Move from actor based to layer based processing
  • Graceful Shutdown
  • Allow other types of executors apart from Tokio
  • Mock/Test Worker
  • Improve monitoring
  • Add job progress via layer
  • Add more sources

v 0.3

  • Standardize API (Storage, Worker, Data, Middleware, Context )
  • Introduce SQL
  • Implement layers for Sentry and Tracing.
  • Improve documentation
  • Organized modules and features.
  • Basic Web API Interface
  • Sql Examples
  • Sqlx migrations

v 0.2

  • Redis Example
  • Actix Web Example

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

Authors

See also the list of contributors who participated in this project.

License

This project is licensed under the MIT License - see the LICENSE.md file for details

apalis's People

Contributors

autotaker avatar azhicham avatar derekleverenz avatar geofmureithi avatar jayvdb avatar kdesjard avatar killix avatar renovate[bot] avatar robjtede avatar sandhose avatar utterstep avatar vasyl-purchel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

apalis's Issues

Consider allocating a static worker id.

The current implementation allocates a random worker-id from uuid. This works well but may produce undefined behaviour which doesn't affect the processing of jobs but may show invalid data when queried the number of workers.
See #41

Rerunning dead jobs in Mysql is not implemented

The storage api for mysql doesnt support this feature.

    // Worker not seen in 5 minutes yet has running jobs
    StorageWorkerPulse::RenqueueOrpharned { count: _ } => {
    //...
    Ok(true)

The goal is to rewrite the query at mysql.rs#L202 to be Mysql compatible and uncomment it.

Can I use this with the sqlx feature `runtime-tokio-native-tls`?

Hi, firstly thanks for the great library - it looks great and I'd love to use it.

I have a project in mind that I'd like to use it on, but the project is using the sqlx feature runtime-tokio-native-tls and this seems to conflict with apalis with the error:

only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', 'runtime-tokio-rustls'] can be enabled

I don't think I can easily get round this requirement of using sqlx with the feature runtime-tokio-native-tls enabled in this particular project.

I'm fairly new to rust so sorry if I'm missing something obvious, but is it possible to use this library in a manner compatible with the sqlx feature runtime-tokio-native-tls?

Integration tests for each backend

Currently integrations tests are running on CI only for MySQL backend.

Let's add integration tests for the following backends

Let's strive to have different pull requests for each storage. This will help in collaboration.
Also before starting on any backend, please add a comment and assigned.

Update `sqlx`

Hello, this project depends on sqlx = "^0.6". But the latest version is 0.7. This is introducing errors in the dependency graph. Would it be possible to update the dependency?

Poll multiple job types on the same PostgreSQL connection

Currently, each instance of PostgresStorage grabs a connection to LISTEN for new jobs. This means that if you have 10 workers, you constantly consume 10 connections to listen on the exact same topic.

Connections can be notably expensive with Postgres, so it would be nice if multiple PostgresStorage could share the same notification stream (maybe through a tokio::sync::watch channel or similar?) instead of each grabbing a connection for each worker.

Ability to easily pipeline.

The concept would be something like:

.build_pipeline(Pipeline::new(start_here).then_daily((do_this, and_this_too)).until(complete))

This would be a sort of persistent job that is running for a long period of time.

Axum application stuck after SIGINT and needs to be killed with SIGTERM

When trying to terminate the application with Ctrl + C (or by sending SIGINT directly) the monitor shuts down, but the application is stuck until it receives a SIGTERM signal.

$ cargo run --package axum-example
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/axum-example`
2022-10-07T13:33:36.526309Z DEBUG axum_example: listening on 127.0.0.1:3000
2022-10-07T13:33:36.526681Z DEBUG apalis_core::worker::monitor: Listening shut down command (ctrl + c)
2022-10-07T13:33:46.759826Z DEBUG apalis_core::worker::monitor: Workers shutdown complete
^C^C

I tested this with the axum example and my own app, both have the same result.

The actix-web example does properly exit, so maybe axum needs some special handling for signals.

$ cargo run --package actix-web-example
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/actix-web-example`
[2022-10-07T13:40:11Z INFO  actix_server::builder] Starting 4 workers
[2022-10-07T13:40:11Z INFO  actix_server::server] Actix runtime found; starting in Actix runtime
[2022-10-07T13:40:11Z DEBUG apalis_core::worker::monitor] Listening shut down command (ctrl + c)
[2022-10-07T13:40:13Z INFO  actix_server::server] SIGINT received; starting forced shutdown
[2022-10-07T13:40:13Z INFO  actix_server::worker] Shutting down idle worker
[2022-10-07T13:40:13Z DEBUG actix_server::accept] Paused accepting connections on 127.0.0.1:8000
[2022-10-07T13:40:13Z INFO  actix_server::worker] Shutting down idle worker
[2022-10-07T13:40:13Z INFO  actix_server::accept] Accept thread stopped
[2022-10-07T13:40:13Z INFO  actix_server::worker] Shutting down idle worker
[2022-10-07T13:40:13Z INFO  actix_server::worker] Shutting down idle worker
[2022-10-07T13:40:13Z DEBUG apalis_core::worker::monitor] Workers shutdown complete

Add example with async_graphql

It would be really helpful if an example with async graphql was present. I am using axum and async graphql and I can not access the storage in the gql_ctx. I can not figure out how to push a new job.

how to set many Workers?

I want to build multiple async function works. How can I do this? The following cannot be achieved

Monitor::new()
    .register_with_count(6, move |_| {
        WorkerBuilder::new(worker_storage.clone())
            .layer(SentryJobLayer)
            .layer(TraceLayer::new())
            .build_fn(queue::send_email)
            .build_fn(queue::send_message)
            .build_fn(queue::and_more)
    })

osv-scanner results

https://github.com/google/osv-scanner finds a lot of security issues in the dependencies.
Would be a good idea to bump a lot of the deps to prevent these.

osv-scanner --lockfile Cargo.lock
Scanned /home/jayvdb/rust/apalis/Cargo.lock file and found 418 packages
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ OSV URL (ID IN BOLD)                โ”‚ ECOSYSTEM โ”‚ PACKAGE         โ”‚ VERSION โ”‚ SOURCE     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ https://osv.dev/RUSTSEC-2021-0145   โ”‚ crates.io โ”‚ atty            โ”‚ 0.2.14  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/GHSA-qc84-gqf4-9926 โ”‚ crates.io โ”‚ crossbeam-utils โ”‚ 0.7.2   โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/RUSTSEC-2022-0041   โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/RUSTSEC-2021-0141   โ”‚ crates.io โ”‚ dotenv          โ”‚ 0.15.0  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/GHSA-jw36-hf63-69r9 โ”‚ crates.io โ”‚ libsqlite3-sys  โ”‚ 0.24.2  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/RUSTSEC-2022-0090   โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/GHSA-5wg8-7c9q-794v โ”‚ crates.io โ”‚ lock_api        โ”‚ 0.3.4   โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/GHSA-gmv4-vmx3-x9f3 โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/GHSA-hj9h-wrgg-hgmx โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/GHSA-ppj3-7jw3-8vc4 โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/GHSA-vh4p-6j7g-f4j9 โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/RUSTSEC-2020-0070   โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ”‚ https://osv.dev/RUSTSEC-2020-0016   โ”‚ crates.io โ”‚ net2            โ”‚ 0.2.38  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/GHSA-fg7r-2g4j-5cgr โ”‚ crates.io โ”‚ tokio           โ”‚ 0.1.22  โ”‚ Cargo.lock โ”‚
โ”‚ https://osv.dev/RUSTSEC-2021-0124   โ”‚           โ”‚                 โ”‚         โ”‚            โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Edited/Blocked

These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

cargo
Cargo.toml
  • document-features 0.2
  • tokio 1
  • async-std 1
  • tower 0.4
  • tracing-futures 0.2.5
  • sentry-core 0.34.0
  • metrics 0.23.0
  • metrics-exporter-prometheus 0.15
  • thiserror 1.0.59
  • futures 0.3.30
  • pin-project-lite 0.2.14
  • uuid 1.8
  • ulid 1
  • serde 1.0
  • tracing 0.1.40
  • criterion 0.5.1
  • pprof 0.13
  • paste 1.0.14
  • serde 1
  • tokio 1
  • redis 0.25.3
  • sqlx 0.7.4
packages/apalis-core/Cargo.toml
  • serde 1.0
  • futures 0.3.30
  • tower 0.4
  • pin-project-lite 0.2.14
  • async-oneshot 0.5.9
  • thiserror 1.0.59
  • ulid 1.1.2
  • futures-timer 3.0.3
  • serde_json 1
  • document-features 0.2
  • tokio 1.37.0
  • tokio-stream 0.1.15
packages/apalis-cron/Cargo.toml
  • cron 0.12.1
  • futures 0.3.30
  • tower 0.4
  • chrono 0.4.38
  • async-stream 0.3.5
  • async-std 1.12.0
  • tokio 1
  • serde 1.0
packages/apalis-redis/Cargo.toml
  • redis 0.25.3
  • serde 1
  • log 0.4.21
  • chrono 0.4.38
  • async-stream 0.3.5
  • futures 0.3.30
  • tokio 1
  • async-std 1.12.0
  • async-trait 0.1.80
  • tokio 1
packages/apalis-sql/Cargo.toml
  • sqlx 0.7.4
  • serde 1
  • serde_json 1
  • log 0.4.21
  • futures 0.3.30
  • async-stream 0.3.5
  • tokio 1
  • futures-lite 2.3.0
  • async-std 1.12.0
  • tokio 1
  • once_cell 1.19.0
github-actions
.github/workflows/bench.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • boa-dev/criterion-compare-action v3
  • postgres 16
  • mysql 8
.github/workflows/cd.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
.github/workflows/ci.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • actions-rs/cargo v1
  • actions-rs/cargo v1
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • actions-rs/cargo v1
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • actions-rs/cargo v1
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • actions-rs/cargo v1
.github/workflows/mysql.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • mysql 8
.github/workflows/postgres.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
  • postgres 16
.github/workflows/redis.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1
.github/workflows/sqlite.yaml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions-rs/toolchain v1

  • Check this box to trigger a request for Renovate to run again on this repository

Lock-free Postgres implementation

Hey - what's the timeline for Lock-free Postgres implementation?

Feel free to outline the rough problem which needs to be solved and we (Bundlr) might be able to solve, depending on time commitment

Autopublish crates to crates.io

Right now I do it manually. It would be nice if it was automatic.
Steps needed:

  1. PR to master
  2. Running CI to master, must pass.
  3. PR merge to master
  4. Create new release
  5. Publish crates to crates.io

Redis cancel running task: worker_id?

It looks like to cancel a running task (using Redis as the queue) you need the worker_id and job_id, that latter of which can be obtained via ctx.id() when the task is initiated. However it's not clear how to obtain the worker_id associated with a running job_id.

Is there a way to send dependencies to jobfunction

Hi Thanks for sharing this excellent library to the community.

Currently the job function accepts two arguments: one is Request type and JobContext

I want to share database pool connection to my job function, is there a way I can do it ?

Example:

#[derive(Deserialize, Serialize)]
struct Youtubelink(String);

impl Job for Youtubelink {
    const NAME: &'static str = "youtube-transcript";
}

async fn transcript(
    job: impl Into<Youtubelink>,
    ctx: JobContext,
    // pgpool: &PgPool
) -> Result<YoutubeContent, Serror> {
    let data = Youtube::link(&job.into().0).content().await?;
    // do something with pgpool and data
}

I'm looking something like this where dbpool is created and registered. Later used in handlers

Add documentation and examples of how workers process jobs

Currently it only shows one worker send_email. Would be good to showcase more types of jobs being consumed.

    Monitor::new()
        .register_with_count(5, move |_| {
            WorkerBuilder::new(sqlite.clone())
                .layer(TraceLayer::new())
                .build_fn(send_email)
        })
        .run()
        .await

Update to sqlx 0.6

Currently apalis-sql is using version 0.5 of sqlx this make using it a bit harder than necessary to use in an application using sqlx 0.6.

I'm unable to use the PostgresStorage::new method do to the differing sqlx versions.

Workaround

Pass a connection string to PostgresStorage::connect.

Standardize the process of building new workers

In the example below, there are two ways of building workers, one involving ServiceBuilder and the other involving WorkerBuilder. It should be fixed so that there is only one approach.

fn main() -> Result<()> {
    let storage = RedisStorage::new(redis).await?;

    let schedule = Schedule::from_str("@daily").unwrap();
    let service = ServiceBuilder::new()
        .layer(Extension(storage.clone()))
        .service(job_fn(enqueue_daily));

    let cron_worker = CronWorker::new(schedule, service);
    Monitor::new()
        .register(cron_worker)
        .register_with_count(2, move || {
            WorkerBuilder::new(storage.clone())
                .build_fn(do_distributed_stuff)
        })
        .run()
        .await
        .unwrap();
}

Controlled Polling

Currently polling happens continuously within a specific interval.

Problem

A busy worker should not spend resources competing for jobs with other workers who might be idle.

Solution

  1. Add a method to WorkerContext that checks if worker is busy.
  2. Pass WorkerContext instead of WorkerId into consume_jobs(&ctx)
  3. Use the is_idle/is_busy method in the polling logic.

Examples for deleting, rescheduling and updating a scheduled job

I searched all the examples in this repo but could not find any sample code to kill, reschedule and update_by_id.

My use case is that I want to schedule a job and then edit the time it needs to run later (I think this should be reschedule?). I would also like to know how to change the information parameter sent to a scheduled job (I think it's the update_by_id method). Finally, another method to completely kill a scheduled job.

New features

Hello

First of all thanks for your amazing work !!
Here are some features that could be really amazing

  • Composition / Pipeline like in Dramatiq, or may be add a way to send a task B within a task A
  • Handle sigterm signal and do a warm shutdown eg stop listening for more jobs and wait for active job to finish
  • Add priority to message handling
  • Is there a way to do some unit testing with mocking or a stub (eg without running a Redis for example)

Few questions btw, does a worker run in a dedicated thread ?
Is it possible to run multiple instance of worker app ?

Improve docs on Fn structure

Currently we have docs showing the main way of building worker job functions:

async fn do_job(job: Job, ctx: JobContext) -> Result<JobResult, JobError> {
   ....
}

Nonetheless, apalis can accept more than that, eg:

async fn do_job(job: Job, ctx: JobContext) {
   ....
}

We should be able to support anyhow::Result too.

v0.4 Milestone

  • Push job need to return id.
  • Add functionality to push multiple jobs.
  • Add documentation on job function structure.

Workers not acknowledging after upgrate to v0.4

{
    "job": null,
    "context": {
        "id": "JID-01H1F31FYEDKGH94XQEDX4ZSYA",
        "status": "Pending",
        "run_at": "2023-05-27T17:10:33.166137782Z",
        "attempts": 0,
        "max_attempts": 25,
        "last_error": null,
        "lock_at": null,
        "lock_by": null,
        "done_at": null
    }
}

{"v":0,"name":"majinn","msg":"Sending email: EmailApalis","level":30,"hostname":"samuel","pid":87291,"time":"2023-05-27T17:10:32.792390087Z","line":23}

I'm getting this from redis and the trace respectively. Any hints on what could be?

Worker stop handle jobs if redis is disconnected

Hello in some case as follow a worker may stop listening for new tasks, this happens when the connection between the worker & redis is interrupted.

Start a worker :

pub async fn send_email(job: Email, _ctx: JobContext) -> impl IntoJobResponse {
    println!("sleeping");
    let dur = time::Duration::from_millis(5_000);
    thread::sleep(dur);
    println!("job number {}", job.text);
}

#[tokio::main]
async fn main() -> Result<()> {
    let storage = RedisStorage::connect("redis://127.0.0.1/").await.expect("");
    Monitor::new()
        .register(
            WorkerBuilder::new(storage.clone())
                .build_fn(send_email),
        )
        .run()
        .await
}

Then with a client send 20 tasks for example.

Once some job are processed, restart redis, under macOS: brew service restart redis.
If the worker was already processing a job (eg in our example sleeping ...)
then the worker will finish this task and will NOT handle the rest of 20 initial tasks.

But you can start another worker in parallel and it will handle the reste of the task without any issue.

If this issue can be fixed this will allow enable the Try Reconnect feature in the middle of application and also at startup (celery style)

Executor defined Sleeper

apalis uses sleepers when polling. The current sleeper implementation uses cfg and is not optimal.
In the next version, it might be good to have these together with Executor.

trait Executor {
   fn spawn(&self, future: F);
   fn sleep(&self) -> SleepFuture;
}

apalis-core crash from any error.

Change email-service:

pub async fn send_email(job: Email, _ctx: JobContext) -> Result<JobResult, JobError> {
    log::info!("Attempting to send email to {}", job.to);
    Err(JobError::WorkerCrashed)
}
2022-12-22T05:57:53.733964Z  INFO job{job_id="b1ad3d63-0fc1-4c65-8c38-e071b9f51344" current_attempt=1}: email_service: Attempting to send email to 1    
2022-12-22T05:57:53.734014Z ERROR job{job_id="b1ad3d63-0fc1-4c65-8c38-e071b9f51344" current_attempt=1}: apalis_core::layers::tracing::on_failure: Job Failed: Attempted to communicate with a crashed background worker done_in=0 ms


async fn handle(&mut self, job: JobRequestWrapper<T>) -> Self::Result {
        match job.0 {
            Ok(Some(job)) => {
                self.handle_job(job).await.unwrap(); << THIS LINE 
            }
            Ok(None) => {
                // on drain
            }
            Err(_e) => {
                todo!()
            }
        };
    }


thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: Failed(WorkerCrashed)', /home/dan/tests/apalis/packages/apalis-core/src/worker/mod.rs:351:44

Bug with SQLite storage

The only example with multiple jobs is the examples/rest-api, but it uses a different storage type for each job. I have a requirement wherein my application has multiple jobs but I want to use the same database for each job. This does not seem possible for now, since when I do this, new jobs are often not deployed.

Right now I am creating a new in-memory database for each job I have but I would like to store them on an actual sqlite database. How can I do this?

Cron jobs and timezone handling

I have a docker container which inherits from scratch. I have an apalis cron job which is supposed to execute at 0000 hrs and 1200 hrs everyday. It does execute but at exactly 0530 hrs for me (I live in Asia/Kolkata timezone which has that offset). How can I configure apalis so that it executes at 0000 hrs of my timezone?

Setting the TZ env variable does not work, probably due to the scratch docker environment.

I'm not really sure if this problem even belongs to this repository, but would love any pointers you'd have on debugging this issue.

Jobs scheduled using `RedisStorage::schedule` are not running at all

I started exploring this crate and attempted the following:

use std::ops::Add;

use apalis::{layers::Extension, prelude::*, redis::RedisStorage};
use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
struct Event {
    id: u64,
}

impl Job for Event {
    const NAME: &'static str = "test::Event";
}

async fn produce_jobs(mut storage: RedisStorage<Event>) -> anyhow::Result<()> {
    for index in 0..5 {
        tracing::info!("Scheduling event: {}", index);
        let time = Utc::now().add(Duration::seconds((2 * (index + 1)) as i64));
        storage.schedule(Event { id: index }, time).await?
    }
    Ok(())
}

async fn handle_event(job: Event, _ctx: JobContext) -> Result<JobResult, JobError> {
    tracing::info!("Handling event: {:?}", job);
    Ok(JobResult::Success)
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();

    let storage = RedisStorage::connect("redis://127.0.0.1/").await?;

    let storage_clone = storage.clone();
    let _ = tokio::spawn(async move {
        tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
        produce_jobs(storage_clone).await.ok();
    });

    Monitor::new()
        .register(
            WorkerBuilder::new(storage.clone())
                .layer(Extension(storage.clone()))
                .build_fn(handle_event),
        )
        .run()
        .await
}

The events queued after the app is started do not get handled at all. Restarting the application does handle them retroactively though. I was expecting that after 3 seconds one queued event would be handled every 2 seconds. Is there something I am missing here?

I want to buy you a coffee!

I might be blind but it would be cool to add a tip/"buy a coffee" etc thing in your README. I'd definitely contribute!

Remove actor paradigm and go the layers way.

Currently, version 0.3 is designed with the actor model in mind. This works ok but has several problems like in #42 and #41.
The next version will lose the following:

  • Actors
  • Broker
  • Pubsub

Rethinking these issues, we can see that a better approach is to use tower layers.
We can change these to optional layers eg:

  • StorageActionLayer
  • RedisPubsubLayer
  • PostgresPubsubLayer

creating jobs from within job - pipelining

Hi, i really like the crate,

i'm looking for a way to create a pipeline, where i can post jobs from another job.
i can pass it as an Extension, but is there another way?

Extension does not work?

I cant find any apalis examples with using extensions for shared data.

Only a wrong example in docs:

/// Extension data for jobs.
///
/// forked from [axum::Extensions]
/// # In Context
///
/// This is commonly used to share state across jobs.
///
/// ```rust,ignore
/// use apalis::{
///     Extension,
///     WorkerBuilder,
///     JobContext
/// };
/// use std::sync::Arc;
///
/// // Some shared state used throughout our application
/// struct State {
///     // ...
/// }
///
/// async fn email_service(email: Email, ctx: JobContext) {
///     let state: &Arc<State> = ctx.data_opt().unwrap();
/// }
///
/// let state = Arc::new(State { /* ... */ });
///
/// let worker = WorkerBuilder::new(storage)
///     .layer(Extension(state))
///     .build_fn(email_service);
/// ```

Where even

use apalis::{
    Extension,
     WorkerBuilder,
     JobContext
 };

is throwing error apalis does not have Extension.

Please make a working example with Shared state.

Fails to run, if there's a migrations folder in the project root

I have my own migrations folder in the root of the project. Apalis fails to run workers on database migration error.

    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/controller`
Error: Database("migration 20230420060200 was previously applied but is missing in the resolved migrations")

Note 20230420060200 is my project's migration file.

No Done jobs

Hi

I test apalis (mysql backend) with 2 workers and consistently add new jobs.

Tens of jobs is done but in database jobs table is 2 jobs only.
Their id is changing - but no one job saved as done.
Why?

2JobsApalis_Screenshot_2023-02-04_18-40-49

Usage of sqlx migration with postgres

It is currently not possible to use apalis on the same postgres database as the main application.

This is do to the sqlx Migrator currently not support multiple applications using the same database. As the postgres migrations are already using a separate schema, it would be quite nice to be able to setup apalis in the same database as the main application it self.

I currently see two options:

  1. Users have to copy our migrations into there sqlx migration folder.
  2. We don't use the sqlx migrator for postgres until sqlx supports mutliple applications in the same database.

This isn't a problem for sqlite or mysql as either of these support postgresql like schemas.

See: launchbadge/sqlx#1698

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.