Getting Started: Scheduling Jobs

Note: this article is out of date. For more recent instructions on using scheduled jobs, see the tutorial.

This article covers job schedulding in Immutant, and is part of our getting started series of tutorials.

Jobs in Immutant are simply functions that execute on a recurring schedule. They fire asynchronously, outside of the thread where they are defined, and fire in the same runtime as the rest of the application, so have access to any shared state.

Jobs are built on top of the Quartz library, and support scheduling via a cron-like specification.

Why would I use this over quartz-clj or calling Quartz directly?

I'm glad you asked! There are several reasons:

  • Immutant abstracts away the complexity of Quartz's internals, so you don't have to worry about managing Schedulers and creating JobDetails, and provides enough functionality for a majority of use cases. For cases where you need advanced scheduling functionality, you can still use quartz-clj or the Quartz classes directly.
  • If you are using Immutant in a cluster, jobs that should fire only once per cluster (aka 'singleton jobs') are handled automatically (see below).
  • When your application is undeployed, your jobs are automatically unscheduled. Note that if you use quartz-clj or Quartz directly from your application, you'll need to clean up after yourself so you don't leave jobs lingering around since Immutant can't automatically unschedule them for you.

Scheduling Jobs

Scheduling a job is as simple as calling the schedule function from the immutant.jobs namespace:

(require '[immutant.jobs :as jobs])
(jobs/schedule "my-job-name" "*/5 * * * * ?" 
                #(println "I was called!"))

The schedule function requires three arguments:

  • name - the name of the job.
  • spec - the cron-style specification string (see below).
  • f - the zero argument function that will be invoked each time the job fires.

Job scheduling is dynamic, and can occur anywhere in your application code. Jobs that share the lifecycle of your application are idiomatically placed in immutant.clj.

You can safely call schedule multiple times with the same job name - the named job will rescheduled.

Cron Sytanx

The spec attribute should contain a crontab-like entry. This is similar to cron specifications used by Vixie cron, anacron and friends, but includes an additional field for specifying seconds. It is composed of 7 fields (6 are required):

SecondsMinutesHoursDay of MonthMonthDay of WeekYear
0-590-590-231-311-12 or JAN-DEC1-7 or SUN-SAT1970-2099 (optional)

For several fields, you may denote subdivision by using the forward-slash (/) character. To execute a job every 5 minutes, */5 in the minutes field would specify this condition.

Spans may be indicated using the dash (-) character. To execute a job Monday through Friday, MON-FRI should be used in the day-of-week field.

Multiple values may be separated using the comma (,) character. The specification of 1,15 in the day-of-month field would result in the job firing on the 1st and 15th of each month.

Either day-of-month or day-of-week must be specified using the ? character, since specifying both is contradictory.

See the Quartz cron specification for additional details.

Unscheduling Jobs

Jobs can be unscheduled via the unschedule function:

(require '[immutant.jobs :as jobs])
    
(jobs/unschedule "my-job-name")

The unschedule function requires one argument:

  • name - the name of a previously scheduled job.

If the given name resolves to an existing job, that job will be unscheduled and the call will return true, otherwise nil is returned.

Jobs are automatically unscheduled when your application is undeployed.

Clustering

When using Immutant in a cluster, you'll need to mark any jobs that should only be scheduled once for the entire cluster with the :singleton option:

(require '[immutant.jobs :as jobs])
(jobs/schedule "my-job-name" "*/5 * * * * ?" 
                #(println "I only fire on one node")
                :singleton true)

If :singleton is true, the job will be scheduled to run on only one node in the cluster at a time. If that node goes down, the job will automatically be scheduled on another node, giving you failover. If :singleton is false or not provided, the job will be scheduled to run on all nodes where the schedule call is executed.

Look for a future post in our Getting Started series on using Immutant in a cluster.

The Future

Currently, jobs can only be scheduled using CronTrigger functionality. We plan to add support for SimpleTrigger functionality at some point in the future, allowing you to do something similar to:

(require '[immutant.jobs :as jobs])
(jobs/schedule "my-at-job" (jobs/every "3s" :times 5)
                #(println "I fire 5 times, every 3 seconds"))

Since Immutant is still in a pre-alpha state, none of what I said above is set in stone. If anything does change, We'll update this post to keep it accurate.

If you have any feedback or questions, get in touch!

Getting Started: Messaging

Happy 2012! For the next installment of our getting started series we'll explore the messaging abstractions available to your Clojure apps when deployed on Immutant. Because Immutant is built atop JBoss AS7, it includes the excellent HornetQ messaging service built right in. Hence, there is nothing extra to install or configure in order for your applications to benefit from asynchronous messaging.

Destinations are either Queues or Topics

Two types of message destinations, or endpoints, are supported: queues and topics. A queue exhibits point-to-point semantics: a message sent to a queue will be delivered to a single recipient. A topic provides publish-subscribe semantics: messages sent to a topic will be delivered to all subscribed recipients. In both cases, the message producers have no direct knowledge of the message consumers.

Use the start function to define a messaging destination. A simple naming convention designates an endpoint as either a queue or a topic: if its name begins with /queue, it's a queue; if it begins with /topic, it's a topic.

(require '[immutant.messaging :as msg])
(msg/start "/queue/work")   ; to start a queue
(msg/start "/topic/news")   ; to start a topic

You can invoke start from anywhere in your application, but typically it's done in the immutant.clj initialization file, as described in an earlier tutorial.

While start has a complement, stop, you needn't call it directly. It will be invoked when your application is undeployed. And it's important to note that start is idempotent: if an endpoint has already been started, likely by a cooperating application, the call is effectively a no-op. Similarly, a call to stop will silently fail if the endpoint is in use by any other application. The last to leave will turn the lights out.

One Way to Produce Messages

publish

Messages are sent to a destination, whether queue or topic, via a single function, publish, to which is passed the destination name and the message content, which can be just about anything. A number of optional key-value parameters may be passed as well.

  • :encoding may be either :clojure (the default), :json (useful with non-clojure consumers) or :text (no encoding)
  • :priority may be an integer between 0-9, inclusive. Convenient keyword values :low, :normal, :high and :critical correspond to 0, 4, 7 and 9, respectively. 4 is the default.
  • :ttl time-to-live may be specified in milliseconds, after which time the message is discarded if not consumed. Default is 0, i.e. forever.
  • :properties is a hash of arbitrary message metadata upon which JMS selector expressions may be constructed to filter received messages.

Some examples:

;; A simple string
(msg/publish "/queue/work" "simple string")
;; Notify everyone something interesting just happened
(msg/publish "/topic/news" {:event "VISIT" :url "/sales-inquiry"})
;; Move this message to the front of the line
(msg/publish "/queue/work" some-message :priority :high :ttl 1000)
;; Make messages as complex as necessary
(msg/publish "/queue/work" {:a "b" :c [1 2 3 {:foo 42}]})
;; Make messages consumable by a Ruby app
(msg/publish "/queue/work" {:a "b" :c [1 2 3 {:foo 42}]} :encoding :json)

Three Ways to Consume Messages

receive

Block on a call to receive, passing a destination name and optionally, the following:

  • :timeout an expiration in milliseconds, after which nil is returned. Default is 0, i.e. wait forever
  • :selector a JMS expression used to filter messages according to the values of arbitrary :properties. For documentation on JMS selector syntax please see the javadoc for javax.jms.Message.

listen

Pass a destination name and function to listen and the decoded content of a message sent to that destination will be passed to the function. Options include:

  • :concurrency the maximum number of listening threads that can simultaneouly call the function. Default is 1.
  • :selector same as :receive

message-seq

Create a lazy sequence of messages via message-seq, which accepts the same options as receive.

Some examples:

;; Wait on a task
(let [task (msg/receive "/queue/work")]
  (perform task))

;; Case-sensitive work queues?
(msg/listen "/queue/lower" #(msg/publish "/queue/upper" (.toUpperCase %)))

;; Contrived laziness
(let [messages (message-seq queue)]
  (doseq [i (range 4)] (publish queue i))
  (= (range 4) (take 4 messages)))

Language Interop

One of our initial goals for Immutant messaging was simple interop between Ruby and Clojure applications deployed on a single platform. TorqueBox Ruby processors already grok the :json encoding and will automatically decode the message into the analogous Ruby data structures, so as long as you limit the content of your messages to standard collections and types, they are transparently interoperable between Clojure and Ruby in either direction. See the overlay post for more details on TorqueBox/Immutant integration.

Of course, the :json encoding enables other JVM-based languages -- anything you could conceivably cram into a war file -- to join in the fun, too. For non-JVM languages or external endpoints, something like the Pipes and Filters API's provided by Clamq could be used since we expose our JMS connection factory as immutant.messaging.core/connection-factory.

Anything Else?

Another advantage we get from AS7 is its clustering support. Once we work out some small integration bits, message distribution across a cluster of dynamic nodes will be automatically load-balanced and fault-tolerant, with minimal to no configuration required.

Of course, we still have other messaging features on our roadmap, e.g. XA transactions, durable subscribers and synchronous request/response, and we're looking for ways to make container-based deployment more developer-friendly, so there's still much to do. Feel free to follow along on Twitter, IRC, or the mailing list.

Hey, you got your Ruby in my Clojure!

UPDATE: This article is obsolete. See the Overlay Tutorial for current documentation and examples.

Recently, we've made some progress toward the promise of a polyglot application server. With the introduction of the Overlay project, it's now very easy to create a single app server capable of deploying both Ruby and Clojure (not to mention Java, of course) applications.

Laying TorqueBox over Immutant

The same Leiningen Immutant plugin that you use to install Immutant may be used to overlay the latest TorqueBox as well:

$ lein immutant overlay torquebox

And voila, your Immutant is suddenly also a TorqueBox! Now we need to set up your environment for TorqueBox development.

$ export TORQUEBOX_HOME=$HOME/.lein/immutant/current
$ export PATH=$TORQUEBOX_HOME/jruby/bin:$PATH

The TorqueBox distribution provides its own JRuby with the TorqueBox gems pre-installed. You can now use the torquebox command to deploy your Ruby applications to your Immutant!

Laying Immutant over TorqueBox

If you'd rather use your own JRuby, and you've already installed the torquebox-server gem, you can overlay the latest Immutant by cloning the Overlay project locally, installing Leiningen and running the following:

$ lein run :overlay $(torquebox env TORQUEBOX_HOME) immutant

All that's left is to set IMMUTANT_HOME and proceed as you normally would:

$ export IMMUTANT_HOME=$(torquebox env TORQUEBOX_HOME)

The Overlay project is capable of overlaying features from any JBoss AS7 distribution onto another one.

Convenient Combo-Pack

For your convenience, we've set up a job on our CI server to overlay the latest Immutant build atop the latest TorqueBox build whenever either is updated. So you can be on the bleeding edge of both projects by downloading and extracting this link:

http://immutant.org/builds/torquebox-immutant.zip

Set both TORQUEBOX_HOME and IMMUTANT_HOME to the extracted directory, and...

Get Your Polyglot On!

Your overlaid server may be started however you're comfortable: either using the Leiningen Immutant plugin, the TorqueBox command or even the standard JBoss commands. It will start up all the apps you've deployed to it, regardless of their language.

If you have any trouble at all, please drop by #torquebox or #immutant on freenode, and we'll get you going.

Getting Started: Installing Immutant v2

Greetings! This article covers the new, improved method for installing Immutant, and replaces the first in the getting started series of tutorials with Immutant. This entry covers setting up a development environment and installing Immutant. This tutorial assumes you are on a *nix system. It also assumes you have Leiningen installed. If not, follow these instructions, then come back here.

Installing the lein plugin

We provide a lein plugin for creating your Immutant applications and managing their life-cycles. As of this post, the latest version of the plugin is 0.3.1. Check clojars for the current version.

Let's install it as a global plugin:

 $ lein plugin install lein-immutant 0.3.1
Copying 3 files to /a/nice/long/tmp/path/lib
Including lein-immutant-0.3.1.jar
Including clojure-1.2.0.jar
Including clojure-contrib-1.2.0.jar
Including data.json-0.1.1.jar
Including fleet-0.9.5.jar
Including overlay-1.0.1.jar
Including progress-1.0.1.jar
Created lein-immutant-0.3.0.jar

Now, run lein immutant to see what tasks the plugin provides:

 $ lein immutant
Manage the deployment lifecycle of an Immutant application.

Subtasks available:
install    Downloads and installs Immutant
overlay    Overlays features onto ~/.lein/immutant/current or $IMMUTANT_HOME
env        Displays paths to the Immutant that the plugin can find
new        Creates a new project skeleton initialized for Immutant
init       Adds a sample immutant.clj configuration file to an existing project
deploy     Deploys the current project to the Immutant specified by ~/.lein/immutant/current or $IMMUTANT_HOME
undeploy   Undeploys the current project from the Immutant specified by ~/.lein/immutant/current or $IMMUTANT_HOME
run        Starts up the Immutant specified by ~/.lein/immutant/current or $IMMUTANT_HOME, displaying its console output

We'll only talk about the install and run tasks in this article - we covered the application specific management tasks in our second tutorial, and cover overlay in its own article.

Installing Immutant

Now we need to install an Immutant distribution. We've yet to make any official releases, but our CI server is setup to publish an incremental build every time we push to the git repo. The latest incremental build is always available at http://immutant.org/builds/immutant-dist-bin.zip. The previous version of this tutorial walked you through downloading and installing the latest incremental manually, but with the new install task, that's no longer necessary - the install task will download and install the latest incremental build for you. Let's see that in action:

 $ lein immutant install
Downloading http://repository-projectodd.forge.cloudbees.com/incremental/immutant/LATEST/immutant-dist-bin.zip
done!                                                                           
Extracting /a/nice/long/tmp/path/lib/immutant-dist-bin.zip
Extracted /Users/tobias/.lein/immutant/releases/immutant-1.x.incremental.51
Linking /Users/tobias/.lein/immutant/current to /Users/tobias/.lein/immutant/releases/immutant-1.x.incremental.51

Part of the install process links the most recently installed version to ~/.lein/immutant/current so the plugin can find the Immutant install without requiring you to set $IMMUTANT_HOME. If $IMMUTANT_HOME is set, it will override the current link.

If you want to install a specific incremental build, specify the build number (available from our builds page) as an argument to lein:

 $ lein immutant install 50

You can also have it install to a directory of your choosing (if you want the latest build, specify 'latest' as the version):

 $ lein immutant install latest /some/other/path

~/.lein/immutant/current will be linked to that location.

Running Immutant

To verify that Immutant is properly installed, let's fire it up. To do so, use the lein immutant run command. This is a convenient way to start the Immutant's JBoss server, and will run in the foreground displaying the console log. You'll see lots of log messages that you can ignore - the one to look for should be the last message, and will tell you the Immutant was properly started:

 $ lein immutant run
Starting Immutant via /Users/tobias/.lein/immutant/current/jboss/bin/standalone.sh
...
(a plethora of log messages deleted)
...
09:18:03,709 INFO  [org.jboss.as] (Controller Boot Thread) JBoss AS 7.1.0.Beta1 "Tesla" started in 2143ms - Started 149 of 211 services (61 services are passive or on-demand)

You can kill the Immutant with Ctrl-C.

Wrapping up

If you've done all of the above, you're now ready to deploy an application. We covered that in our second tutorial.

Since Immutant is still in a pre-alpha state, none of what I said above is set in stone. If anything does change, We'll update this post to keep it accurate.

If you have any feedback or questions, get in touch!

Slides From My DevIgnition Polyglot AS Presentation

This past Friday I had the pleasure of presenting "The Polyglot Future of JBoss AS" to an audience of mostly Java developers at DevIgnition in NoVA.

Thanks to Gray Herter for organizing a great conference, and to Booz Allen Hamilton for sharing their kick-butt conference center.

If you are interested in the code for the demo, that's here.

The talk was recorded, so once that recording is available I'll update this post to point to it.

Here are my slides from that presentation for the enjoyment of all: