├── .gitignore
├── COPYING_LESSER.txt
├── LICENSE.txt
├── Makefile
├── README.md
├── docs
└── reloadable-code.png
├── info.rkt
└── reloadable
└── main.rkt
/.gitignore:
--------------------------------------------------------------------------------
1 | compiled/
2 |
--------------------------------------------------------------------------------
/COPYING_LESSER.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright © 2014 Tony Garnock-Jones
2 |
3 | This program is free software: you can redistribute it and/or modify
4 | it under the terms of the GNU Lesser General Public License as published by
5 | the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser General Public License for more details.
12 |
13 | You should have received a copy of the GNU Lesser General Public License
14 | along with this program. If not, see .
15 |
16 | See the file COPYING_LESSER.txt for the GNU Lesser General Public License.
17 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PACKAGENAME=reloadable
2 | COLLECTS=reloadable
3 |
4 | all: setup
5 |
6 | clean:
7 | find . -name compiled -type d | xargs rm -rf
8 |
9 | setup:
10 | raco setup $(COLLECTS)
11 |
12 | link:
13 | raco pkg install --link -n $(PACKAGENAME) $$(pwd)
14 |
15 | unlink:
16 | raco pkg remove $(PACKAGENAME)
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Code-reloading for Racket
2 |
3 | 
4 |
5 | Racket's built-in `dynamic-rerequire` does the heavy lifting, but
6 | doesn't give a high-level interface to help us build reloadable
7 | servers. This package fills in that gap.
8 |
9 | ### Example
10 |
11 | A complete example of a website written using the
12 | [Racket web-server](http://docs.racket-lang.org/web-server/) is
13 | available at . The
14 | site uses this module to support runtime code-reloading.
15 |
16 | - [`main.rkt`](https://github.com/tonyg/racket-reloadable-example/blob/master/src/main.rkt)
17 | is the permanent part of the server
18 | - [`site.rkt`](https://github.com/tonyg/racket-reloadable-example/blob/master/src/main.rkt)
19 | is the reloadable part of the server
20 |
21 | ## Usage
22 |
23 | 1. Split your server into a *permanent* and a *reloadable* part
24 | 2. Decide which pieces of state in the reloadable part should be *persistent*
25 | 3. Use indirection to access the reloadable part from the permanent part
26 | 4. Decide how and when to reload code
27 |
28 | ### Splitting the server
29 |
30 | It's easiest to make the permanent part of your program as small as
31 | possible. This is because if a module is `require`d by the permanent
32 | part of your program, directly or indirectly, then it will *never* be
33 | reloaded, even if it is also `require`d by the reloadable part.
34 |
35 | Any modules `require`d from the permanent part of your program are
36 | effectively included in the permanent part of the program.
37 |
38 | For example, say your program is started from `main.rkt`, which will
39 | be the permanent part of the application, with the bulk of the program
40 | functionality in the reloadable part, `features.rkt`. Then your
41 | `main.rkt` should be something along the lines of the following:
42 |
43 | ```racket
44 | #lang racket
45 | (require reloadable)
46 | (define main (reloadable-entry-point->procedure
47 | (make-reloadable-entry-point 'start-program "features.rkt")))
48 | (reload!)
49 | (main)
50 | ```
51 |
52 | where `start-program` is `provide`d from `features.rkt`. It is
53 | important that we do not require `features.rkt` from `main.rkt`!
54 | Instead, that is taken care of by the entry-point machinery in this
55 | package.
56 |
57 | You must call `reload!` at least once before accessing a new
58 | entry-point's value.
59 |
60 | You must also ensure that there are no stray `.zo` files for the
61 | reloadable part of your program. If any such `.zo` files exist, they
62 | will interfere with code loading.
63 |
64 | ### Persistent state
65 |
66 | Your `features.rkt` module may have global variables. Some of these
67 | should be initialised every time the module is reloaded, but others
68 | should only be initialised once, at server startup time.
69 |
70 | Global variables that should be reinitialised on every code reload do
71 | not need to be declared differently:
72 |
73 | ```racket
74 | (define module-variable-initialised-every-time
75 | (begin (printf "Reinitialising module-variable-initialised-every-time!\n")
76 | 17))
77 | ```
78 |
79 | Global variables that should be initialised only *once*, at server
80 | startup, should be declared using `make-persistent-state`:
81 |
82 | ```racket
83 | (define some-persistent-variable
84 | (make-persistent-state 'some-persistent-variable
85 | (lambda ()
86 | (printf "Initialising some-persistent-variable!\n")
87 | 42)))
88 | ```
89 |
90 | Note that the first argument to `make-persistent-state` must be unique
91 | across the entire Racket instance. This is arguably a bug: ideally,
92 | it'd only need to be unique to a particular module. A future version
93 | of this library may fix this.
94 |
95 | Read and write persistent state values like you would parameters:
96 |
97 | ```racket
98 | ;; Access it
99 | (printf "Current some-persistent-variable value: ~a"
100 | (some-persistent-variable))
101 | ;; Set it to a new value
102 | (some-persistent-variable (compute-new-value))
103 | ```
104 |
105 | ### Use `#:prefab` structs for persistent state
106 |
107 | Make sure you use
108 | [`#:prefab` structs](http://docs.racket-lang.org/guide/define-struct.html?q=prefab#%28part._prefab-struct%29)
109 | for your persistent state:
110 |
111 | ```racket
112 | (struct my-state-vector (field1 field2) #:prefab)
113 | ```
114 |
115 | If you use *non*-prefab structs for persistent state, any newly-loaded
116 | code won't be able to recognise structs that were created by previous
117 | versions of the code.
118 |
119 | The reason for this is that non-prefab structs in Racket are
120 | *generative*, meaning that each time your code is reloaded, a new set
121 | of struct types are created.
122 |
123 | This transcript shows the problem:
124 |
125 | Welcome to Racket v6.1.1.4.
126 | -> (struct x () #:transparent)
127 | -> (define x1 (x))
128 | -> (struct x () #:transparent)
129 | -> (define x2 (x))
130 | -> (x? x2)
131 | #t
132 | -> (x? x1)
133 | #f
134 | -> (equal? x1 x2)
135 | #f
136 |
137 | With
138 | [prefab structs](http://docs.racket-lang.org/guide/define-struct.html?q=prefab#%28part._prefab-struct%29),
139 | however, the problem goes away:
140 |
141 | Welcome to Racket v6.1.1.4.
142 | -> (struct x () #:prefab)
143 | -> (define x1 (x))
144 | -> (struct x () #:prefab)
145 | -> (define x2 (x))
146 | -> (x? x2)
147 | #t
148 | -> (x? x1)
149 | #t
150 | -> (equal? x1 x2)
151 | #t
152 |
153 | Struct definitions from the permanent part of your program will never
154 | be reloaded, of course, so this warning doesn't apply to them. Pre-
155 | and post-reload routines share the same struct definition in that
156 | case. Likewise, struct definitions whose instances never survive
157 | across a code-reloading (i.e. that are never placed in the program's
158 | persistent state) can be non-prefab.
159 |
160 | ### Accessing reloadable code from permanent code
161 |
162 | Use the entry points you create with `make-reloadable-entry-point`
163 | (which you may also retrieve after they are created by calling
164 | `lookup-reloadable-entry-point`).
165 |
166 | Each time `reload!` is called, the `reloadable-entry-point-value` of
167 | each entry point is recomputed from the new versions of each module.
168 |
169 | - If an entry point holds a procedure, you can
170 | - extract its value and call it directly, or
171 | - use `reloadable-entry-point->procedure` to convert an entry-point
172 | into a general procedure that reflects the calling conventions of
173 | the underlying procedure.
174 |
175 | - If an entry point holds any other kind of value, you can use
176 | `reloadable-entry-point-value` to access it.
177 |
178 | ### Controlling code reloading
179 |
180 | Direct calls to `reload!` force immediate reloading of any changed
181 | code, subject to the caveats about the split between the permanent and
182 | reloadable parts of your program given above.
183 |
184 | In addition, by default, the reloadable part of your program is
185 | scanned constantly for changes, and whenever the system notices that a
186 | `.rkt` file in the reloadable part of your program has changed, it
187 | will automatically be recompiled and reloaded.
188 |
189 | To disable this automatic scanning, call
190 |
191 | ```racket
192 | (set-reload-poll-interval! #f)
193 | ```
194 |
195 | If automatic scanning is disabled, then calls to `reload!` will be the
196 | only way to make code reloading happen.
197 |
198 | ### Reload hooks
199 |
200 | You can use `add-reload-hook!` and `remove-reload-hook!` to install or
201 | remove a "reload hook", a procedure to be called every time a reload
202 | completes, if that reload actually loaded any new or changed code. Each
203 | hook procedure is called with a dictionary mapping a module path to a list
204 | of reloaded source file paths.
205 |
206 | ## Copyright and License
207 |
208 | Copyright © 2014 Tony Garnock-Jones
209 |
210 | This program is free software: you can redistribute it and/or modify
211 | it under the terms of the GNU Lesser General Public License as published by
212 | the Free Software Foundation, either version 3 of the License, or
213 | (at your option) any later version.
214 |
215 | This program is distributed in the hope that it will be useful,
216 | but WITHOUT ANY WARRANTY; without even the implied warranty of
217 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
218 | GNU Lesser General Public License for more details.
219 |
220 | You should have received a copy of the GNU Lesser General Public License
221 | along with this program. If not, see .
222 |
--------------------------------------------------------------------------------
/docs/reloadable-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tonyg/racket-reloadable/47c52635d682f66d96e9c7d49bc3763fb4782235/docs/reloadable-code.png
--------------------------------------------------------------------------------
/info.rkt:
--------------------------------------------------------------------------------
1 | #lang setup/infotab
2 | (define collection 'multi)
3 | (define deps '("base"))
4 |
--------------------------------------------------------------------------------
/reloadable/main.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 |
3 | (provide (struct-out reloadable-entry-point)
4 | reload-poll-interval
5 | set-reload-poll-interval!
6 | reload-failure-retry-delay
7 | reload!
8 | make-reloadable-entry-point
9 | lookup-reloadable-entry-point
10 | reloadable-entry-point->procedure
11 | make-persistent-state
12 | add-reload-hook!
13 | remove-reload-hook!)
14 |
15 | (require racket/set)
16 | (require racket/match)
17 | (require racket/rerequire)
18 |
19 | (define reload-poll-interval 0.5) ;; seconds
20 | (define reload-failure-retry-delay (make-parameter 5)) ;; seconds
21 |
22 | (struct reloadable-entry-point (name
23 | module-path
24 | identifier-symbol
25 | on-absent
26 | [value #:mutable])
27 | #:prefab)
28 |
29 | (define reloadable-entry-points (make-hash))
30 | (define persistent-state (make-hash))
31 | (define reload-hooks (make-hasheq))
32 |
33 | (define (set-reload-poll-interval! v)
34 | (set! reload-poll-interval v))
35 |
36 | (define (reloader-main)
37 | (let loop ()
38 | (match (sync (handle-evt (thread-receive-evt)
39 | (lambda (_) (thread-receive)))
40 | (if reload-poll-interval
41 | (handle-evt (alarm-evt (+ (current-inexact-milliseconds)
42 | (* reload-poll-interval 1000)))
43 | (lambda (_) (list #f 'reload)))
44 | never-evt))
45 | [(list ch 'reload)
46 | (define result (do-reload!))
47 | (when (not result) (sleep (reload-failure-retry-delay)))
48 | (when ch (channel-put ch result))])
49 | (loop)))
50 |
51 | (define reloader-thread (thread reloader-main))
52 |
53 | (define (reloader-rpc . request)
54 | (define ch (make-channel))
55 | (thread-send reloader-thread (cons ch request))
56 | (channel-get ch))
57 |
58 | (define (reload!) (reloader-rpc 'reload))
59 |
60 | ;; Only to be called from reloader-main
61 | (define (do-reload!)
62 | (define module-paths (for/set ((e (in-hash-values reloadable-entry-points)))
63 | (reloadable-entry-point-module-path e)))
64 | (with-handlers ((exn:fail?
65 | (lambda (e)
66 | (log-error "*** WHILE RELOADING CODE***\n~a"
67 | (parameterize ([current-error-port (open-output-string)])
68 | ((error-display-handler) (exn-message e) e)
69 | (get-output-string (current-error-port))))
70 | #f)))
71 | (define changed-paths
72 | (for/fold [(h (hash))] ((module-path (in-set module-paths)))
73 | (let ((paths (dynamic-rerequire module-path #:verbosity 'all)))
74 | (if (null? paths)
75 | h
76 | (hash-set h module-path paths)))))
77 | (for ((e (in-hash-values reloadable-entry-points)))
78 | (match-define (reloadable-entry-point _ module-path identifier-symbol on-absent _) e)
79 | (define new-value (if on-absent
80 | (dynamic-require module-path identifier-symbol on-absent)
81 | (dynamic-require module-path identifier-symbol)))
82 | (set-reloadable-entry-point-value! e new-value))
83 | (when (not (hash-empty? changed-paths))
84 | (for ((hook (in-hash-keys reload-hooks)))
85 | (hook changed-paths)))
86 | #t))
87 |
88 | (define (make-reloadable-entry-point name module-path [identifier-symbol name]
89 | #:on-absent [on-absent #f])
90 | (define key (list module-path name))
91 | (hash-ref reloadable-entry-points
92 | key
93 | (lambda ()
94 | (define e (reloadable-entry-point name module-path identifier-symbol on-absent #f))
95 | (hash-set! reloadable-entry-points key e)
96 | e)))
97 |
98 | (define (lookup-reloadable-entry-point name module-path)
99 | (hash-ref reloadable-entry-points
100 | (list module-path name)
101 | (lambda ()
102 | (error 'lookup-reloadable-entry-point
103 | "Reloadable-entry-point ~a not found in module ~a"
104 | name
105 | module-path))))
106 |
107 | (define (reloadable-entry-point->procedure e)
108 | (make-keyword-procedure
109 | (lambda (keywords keyword-values . positionals)
110 | (keyword-apply (reloadable-entry-point-value e)
111 | keywords
112 | keyword-values
113 | positionals))))
114 |
115 | (define (make-persistent-state name initial-value-thunk)
116 | (hash-ref persistent-state
117 | name
118 | (lambda ()
119 | (define value (initial-value-thunk))
120 | (define handler
121 | (case-lambda
122 | [() value]
123 | [(new-value)
124 | (set! value new-value)
125 | value]))
126 | (hash-set! persistent-state name handler)
127 | handler)))
128 |
129 | (define (add-reload-hook! hook)
130 | (hash-set! reload-hooks hook #t))
131 |
132 | (define (remove-reload-hook! hook)
133 | (hash-remove! reload-hooks hook))
134 |
--------------------------------------------------------------------------------