Last week we made our Pub/Sub application use protocol buffer objects for most of its internal state. This week we’ll take advantage of that change by setting startup and shutdown hooks to load state and save state respectively. We will add flags so someone starting up our application can set the load and save files on the command line. We will then package our application into an executable with a new asdf command.
We will use ace.core.hook to implement our load and exit hooks. We will show how to make methods that will run at load and exit time when we use this library in the code below. In the defpackage we use the nickname hook. The library is available in the ace.core repository.
We use ace.flag as our command line flag parsing library. This is a command line flag library used extensively at Google for our lisp executables. The library can be found in the ace.flag repository.
We define three command line flags:
- This flag is used for testing purposes. It should be removed in the future.
The definitions all look the same, we will look at
flag::*load-file* as an example:
(flag:define flag::*load-file* "" "Specifies the file from which to load the PROTO-CACHE on start up." :type string)
- We use the flag:define macro to define a flag. Please see the code for complete documentation of this macro (REAME.md update coming). We only use a small subset of the ace.flag package.
flag::*load-file*: This is the global where the parsed command line flag will be stored.
- The documentation string to document the flag. If
flag:print-helpis called this documentation will be printed:
--load-file (Determines the file to load PROTO-CACHE from on startup)
:type: The type of the flag. Here we have a string.
We use the symbol-name string of the global in lowercase as the command line input.
- flag::*load_file* becomes –load_file
The :name or :names key in the
flag:define macro will let users select their own names for the command line input instead of this default.
We want to create a binary for our application. Since we have no way to add publishers and subscribers outside of the repl we define a dummy main that adds publishers and subscribers for us:
(defun main () (register-publisher "pika" "chu") (register-subscriber "pika" flag::*new-subscriber*) (update-publisher-any "pika" "chu" (google:make-any :type-url "a")) ;; Sleep to make sure running threads exit. (sleep 2))
After running the application we can check for a new subscriber URL in the saved proto-cache application state file. I will show this shortly.
We have several pre-made hooks defined in ace.core.hook. Two useful functions are ace.core.hook:at-restart and ace.core.hook:at-exit. As one can imagine, at-restart runs when the lisp image starts up, and at-exit runs when the lisp image is about to exit.
The first thing we do when we start our application is parse our command line:
(defmethod hook::at-restart parse-command-line () "Parse the command line flags." (flag:parse-command-line) (when flag::*help* (flag:print-help)))
You MUST call
flag:parse-command-line for the defined command line flags to have non default values.
We also print a help menu if
--help was passed in.
Then we can load our proto if the load-file flag was passed in:
(defmethod hook::at-restart load-proto-cache :after parse-command-line () "Load the command line specified file at startup." (when (string/= flag::*load-file* "") (load-state-from-file :filename flag::*load-file*)))
We see an :after clause in our defmethod. We want the load-proto-cache method called during start-up but after we have parsed the command line so
flag::*load-file* has been properly set.
Note: The defmethod here uses a special defmethod syntax added in ace.core.hook. Please see the hook-method documentation for complete details.
Finally we save our image state at exit:
(defmethod hook::at-exit save-proto-cache () "Save the command line specified file at exit." (when (string/= flag::*save-file* "") (save-state-to-file :filename flag::*save-file*)))
The attentive reader will notice our main function never explicitly called any of these hook functions…
We add code to build an executable using asdf:
(defpackage :proto-cache … :build-operation "program-op" :build-pathname "proto-cache" :entry-point "proto-cache:main")
This is a program-op. The executable pathname is relative, we save the binary as “proto-cache” in the same directory as our proto-cache code. The entry point function is proto-cache:main.
We may then call:
sbcl --eval "(asdf:operate :build-op :proto-cache)"
at the command line to create our binary.
Running our binary:
With our binary built we can call:
./proto-cache --save-file /tmp/first.proto --new-subscriber http://www.google.com
pika' http://www.google.com a?pika"chujg
These are serialized values so one shouldn’t try to understand the output so much. We can see “http://www.google.com”, “pika”, and “chu” are all saved.
./proto-cache --load-file /tmp/first.pb --save-file /tmp/first.pb --new-subscriber http://www.altavista.com
I pikaA ?http://www.altavista.com http://www.google.com a?pika"chujg “
Flags from ace.flag: --lisp-global-flags (When provided, allows specifying global and special variables as a flag on the command line. The values are NIL - for none, :external - for package external, and T - for all flags.) Type: ACE.FLAG::GLOBAL-FLAGS --help (Whether to print help) Type: BOOLEAN Value: T --load-file (Determines the file to load PROTO-CACHE from on startup) Type: STRING Value: "" --new-subscriber (URL for a new subscriber, just for testing) Type: STRING Value: "" --lisp-normalize-flags (When non-nil the parsed flags will be transformed into a normalized form. The normalized form contains hyphens in place of underscores, trims '*' characters, and puts the name into lower case for flags names longer than one character.) Type: BOOLEAN --save-file (Determines the file to save PROTO-CACHE from on shutdown) Type: STRING Value: ""
This shows our provided documentation of the command line flags as expected.
Today we added command line flags, load and exit hooks, and made our application buildable as an executable. We can build our executable and distribute it as we see fit. We can direct it to load and save the application state to user specified files without updating the code. There is still much to do before it’s done but this is slowly becoming a usable application.
There are a few additions I would like to make, but I have a second child coming soon. This may (or may not) be my last technical blog post for quite some time. I hope this sequence of Proto Cache posts has been useful thus far, and I hope to have more in the future.
Thanks to Ron Gut and Carl Gay for copious edits and comments.