Table of Contents
In the opening section we looked at foundational building blocks upon which OCurrent is built. In this section we will explore in more depth the interface must users will interact with when building their own OCurrent plugins including:
- Caching
- Monitoring
- Terms
Each of these will then be used when we build the plugin in the next part of this tutorial.
The Current Module
The Current
module provides a lot of the functionality for glueing different parts of your plugin together. For example the map
function.
# #require "current"
# Current.return
- : ?label:string -> 'a -> 'a Current.term = <fun>
# Current.map
- : ('a -> 'b) -> 'a Current.term -> 'b Current.term = <fun>
# Current.(return "hello " |> map (( ^ ) "world"))
- : string Current.term = <abstr>
Jobs in OCurrent
A Job.t
in OCurrent is exactly that, a task to be run. It carries about with it a lot of additional information for OCurrent to use such as its priority
and its start_time
.
OCurrent Caching
Caching is an important aspects to OCurrent pipelines. It can help reduce the amount of recomputation needed even if some input changes and it can really help for complex or lengthy operations (like pulling big docker images). In order to cache things we need to use the Make
functor.
# #require "current.cache"
# #show Current_cache.Make
module Make :
functor (B : Current_cache.S.BUILDER) ->
sig
val get :
?schedule:Current_cache.Schedule.t ->
B.t -> B.Key.t -> B.Value.t Current.Primitive.t
val invalidate : B.Key.t -> unit
val reset : db:bool -> unit
end
From the signature of the returned module you can see we get something very cache-like with function for resetting, invalidating and getting items from our cache. But in order to have our cache we need to provide a builder.
# #show Current_cache.S.BUILDER
module type BUILDER =
sig
type t
val id : string
module Key : Current_cache.S.WITH_DIGEST
module Value : Current_cache.S.WITH_MARSHAL
val build : t -> Current.Job.t -> Key.t -> Value.t Current.or_error Lwt.t
val pp : Key.t Fmt.t
val auto_cancel : bool
end
There are different aspects to our builder than we can see. Firstly, some configuration parameters:
id
-- a uniqueid
for ourBUILDER
auto_cancel
-- a parameter which allows you to specify if an operation should be cancelled if no longer needed (true
) or if only a user should be able to do it (false
).
The pp
function is just the standard Format
style pretty-printing function.
The more interesting parts are the modules Key
and Value
and finally the build
function.
The Key Module
The Key module is used for looking up your entry in the cache much like a Hashtable. It must look like a S.WITH_DIGEST
.
# #show Current_cache.S.WITH_DIGEST
module type WITH_DIGEST = sig type t val digest : t -> string end
Which is a very simple module with one function that takes a key value (t
) and returns a unique string identifying that value in the cache.
Often a nice way to do this is to convert you OCaml type to JSON with something like ppx_deriving_yojson.
The Value Module
The value module is whatever the cache is storing and must look like a S.WITH_MARSHAL
.
# #show Current_cache.S.WITH_MARSHAL
module type WITH_MARSHAL =
sig type t val marshal : t -> string val unmarshal : string -> t end
Another relatively simple module but this time two functions are necessary -- marshal
and unmarshal
. You can think of these functions like save
and load
. You want to be able to turn your value type t
into a string
and then reverse the process. The idea being unmarshal (marshal t) = t
.
The Build Function
Hopefully by exploring these two functions you get a good understanding of how the caching is going to work. The last step is the build function. This takes some configuration parameters but is mostly about generating a value from a key.
Let's have a look at how the Git
plugin does it -- see the repository here.
Ignore the code for working out the level
for now, you see that a task is given to the supplied OCurrent job
. The first thing it does is lock the repository that the key
refers too. Then it tries to make a local copy if one does not already exist, it does so using git_clone
. It then ensures the right commit
is available, if not then it will be fetched before finally returning the commit.