Giter VIP home page Giter VIP logo

citrine's Introduction

๐Ÿ’Ž Citrine energizes every level of life. It cleanses the chakras and opens the intuition. It's one of the most powerful energy crystals in the world. ๐Ÿ’Ž

Citrine is a library for running scheduled jobs on an Erlang cluster, similar to cron. Citrine was created to satisfy a few simple requirements:

  • Provide reliable execution of jobs according to a schedule
  • Keep jobs running in the event of network splits or other failure scenarios
  • Build upon Elixir and Erlang primitives with minimum external dependencies
  • Be easy to operate and reason about
  • Work well in stateless container-based environments such as Kubernetes
  • Distribute jobs globally across a cluster
  • Follow a pattern of convention over configuration and minimize the number of knobs required

Internally Citrine uses Erlang's excellent mnesia module for state and process management. It uses in-memory tables, which comes with certain caveats.

If you're using mnesia elsewhere in your application, you should be aware that Citrine will attempt to join existing mnesia clusters.

What Citrine can and cannot do

Citrine is not a perfect solution for all scheduling needs. There are certain features Citrine does not implement, or even try to implement, and if you require these features you should either look elsewhere or consider different patterns.

  • Best effort: There are some scenarios in which Citrine could potentially miss execution of a job, such as while rolling an update. Citrine will always try its best to execute jobs according to their schedules, but in some cases it may not.
  • Exactly once semantics: Citrine does not attempt to guarantee a job runs exactly once according to its schedule, it prefers to run a job multiple times in the event of failure scenarios such as netsplits.
  • Interruption handling: In the event a job is interrupted during execution, or the job itself fails, Citrine makes no attempt to restart or re-run the job.
  • Idempotency: This is not so much a limitation but rather a warning: your jobs should be designed to be idempotent to prevent strange side effects. Citrine cannot provide any guarantees.
  • Clustering: Citrine assumes you've already specified the cluster topology, either manually or using a library such as libcluster.

If you need stronger guarantees of execution, it's recommended that you use a combination of a stateful queueing service with a worker library, such as Exq.

If you're okay with the limitations of the library, Citrine is a great choice thanks to its simple interface, minimal dependencies and ease of operations.

Installation

The package can be installed by adding citrine to your list of dependencies in mix.exs:

def deps do
  [
    {:citrine, "~> 0.1.0"}
  ]
end

Usage

Create a module for the core scheduler:

defmodule MyApp.Scheduler do
  use Citrine.Scheduler, otp_app: :myapp
end

Add the scheduler to your supervisor tree, for example in application.ex:

defmodule MyApp.Application do
  use Application
  def start(_type, _args) do
    children = [
      # Start your Citrine scheduler
      MyApp.Scheduler
    ]
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Use the scheduler to start and stop a job:

# Create a job
job = %Citrine.Job{
  id: "my_job_id",
  schedule: "* * * * * *", # Run every second
  task: job_task,
  extended_syntax: true # Use extended cron syntax
}
# Start or update a job
MyApp.Scheduler.put_job(job)
# Terminate and delete a job
MyApp.Scheduler.delete_job(job)

For further details, see HexDocs.

citrine's People

Contributors

brndnmtthws avatar

Stargazers

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

Watchers

 avatar  avatar  avatar

citrine's Issues

(FunctionClauseError) no function clause matching in Citrine.JobExecutor.handle_info/2

Failure after first execution:

[debug] finished job id=job_siteguardian.dev in 1.336468s
[debug] terminating Citrine.JobExecutor with reason: {:function_clause, [{Citrine.JobExecutor, :handle_info, [{:EXIT, #Port<0.21>, :normal}, %{cron_expr: ~e[* * * * * *], job: %Citrine.Job{extended_syntax: false, id: "job_siteguardian.dev", schedule: "* * * * *", task: #Function<1.73740159/0 in Siteguardian.Application.start/2>}, registry: Siteguardian.Scheduler.Registry, timer: #Reference<0.3764802182.1941176323.235445>}], [file: 'lib/citrine/job_executor.ex', line: 101]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 680]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 756]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]} and state=%{cron_expr: ~e[* * * * * *], job: %Citrine.Job{extended_syntax: false, id: "job_siteguardian.dev", schedule: "* * * * *", task: #Function<1.73740159/0 in Siteguardian.Application.start/2>}, registry: Siteguardian.Scheduler.Registry, timer: #Reference<0.3764802182.1941176323.235445>}
[error] GenServer {Siteguardian.Scheduler.Registry, "job_siteguardian.dev"} terminating
** (FunctionClauseError) no function clause matching in Citrine.JobExecutor.handle_info/2
    (citrine 0.1.11) lib/citrine/job_executor.ex:101: Citrine.JobExecutor.handle_info({:EXIT, #Port<0.21>, :normal}, %{cron_expr: ~e[* * * * * *], job: %Citrine.Job{extended_syntax: false, id: "job_siteguardian.dev", schedule: "* * * * *", task: #Function<1.73740159/0 in Siteguardian.Application.start/2>}, registry: Siteguardian.Scheduler.Registry, timer: #Reference<0.3764802182.1941176323.235445>})
    (stdlib 3.13.2) gen_server.erl:680: :gen_server.try_dispatch/4
    (stdlib 3.13.2) gen_server.erl:756: :gen_server.handle_msg/6
    (stdlib 3.13.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:EXIT, #Port<0.21>, :normal}
State: %{cron_expr: ~e[* * * * * *], job: %Citrine.Job{extended_syntax: false, id: "job_siteguardian.dev", schedule: "* * * * *", task: #Function<1.73740159/0 in Siteguardian.Application.start/2>}, registry: Siteguardian.Scheduler.Registry, timer: #Reference<0.3764802182.1941176323.235445>}

Not sure what I'm going wrong here or why executing my task fails after the first run

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.