├── .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 | ![Illustration of reloadable program](docs/reloadable-code.png) 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 | --------------------------------------------------------------------------------