├── doc └── intro.md ├── index.html ├── .gitignore ├── src └── cljs │ └── servant │ ├── macros.clj │ ├── worker.cljs │ └── core.cljs ├── project.clj ├── README.md └── epl-v10.html /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to servant 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |Look in the console
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .repl 14 | resources 15 | repl 16 | out 17 | main.js 18 | main.js.map 19 | -------------------------------------------------------------------------------- /src/cljs/servant/macros.clj: -------------------------------------------------------------------------------- 1 | (ns servant.macros) 2 | 3 | (defmacro defservantfn 4 | " Create the function, 5 | Register the function in a worker-fn map" 6 | [fn-sym args & body] 7 | `(do 8 | (defn ~fn-sym ~args ~@body) 9 | (servant.worker/register-servant-fn ~(name fn-sym) ~fn-sym))) 10 | -------------------------------------------------------------------------------- /src/cljs/servant/worker.cljs: -------------------------------------------------------------------------------- 1 | (ns servant.worker 2 | (:require 3 | [cljs.core.async :refer [chan close! timeout ]] 4 | [cljs.reader :as reader]) 5 | (:require-macros [cljs.core.async.macros :as m :refer [go]])) 6 | 7 | (def worker-fn-map (atom {})) 8 | 9 | (defn register-servant-fn [fn-name f] 10 | (swap! worker-fn-map assoc (keyword fn-name) f)) 11 | 12 | (defn run-function-name [message-data] 13 | (let [fn-key (reader/read-string (aget message-data "fn")) 14 | f (get @worker-fn-map fn-key) 15 | args (aget message-data "args")] 16 | (apply f args))) 17 | 18 | (defn post-array-buffer 19 | "In order to send back an array buffer, your function should return 20 | a vector with the result as the first item and the arraybuffers to transfer as the second. 21 | an array of array buffers" 22 | [[result arraybuffers]] 23 | (.postMessage js/self result (clj->js arraybuffers))) 24 | 25 | (defn decode-message [event] 26 | (condp = (aget (.-data event) "command") 27 | "channel" (.postMessage js/self (run-function-name (.-data event))) 28 | "channel-arraybuffer" (post-array-buffer (run-function-name (.-data event))))) 29 | 30 | (defn bootstrap [] 31 | (set! (.-onmessage js/self) decode-message)) 32 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject servant "0.1.5" 2 | :source-paths ["src/cljs"] 3 | :description "A Clojurescript Library for interacting with webworkers sanely" 4 | :url "https://github.com/MarcoPolo/Servant" 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :dependencies [[org.clojure/clojure "1.8.0"] 8 | [org.clojure/clojurescript "1.7.228"] 9 | [org.clojure/core.async "0.2.374"] 10 | [com.cemerick/clojurescript.test "0.0.4"] ] 11 | :plugins [[lein-cljsbuild "1.1.2"]] 12 | :cljsbuild 13 | {:builds 14 | [{:id "servant" 15 | :source-paths ["src/cljs/servant"] 16 | :compiler {:optimizations :advanced 17 | :pretty-print false 18 | :output-to "main.js" 19 | :source-map "main.js.map" 20 | }} 21 | {:id "tests" 22 | :source-paths ["src" "test"] 23 | :compiler {:output-to "target/cljs/testable.js" 24 | :source-map "target/cljs/testable.js.map" 25 | :optimizations :simple 26 | :pretty-print true}} ] 27 | :test-commands {"tests" ["phantomjs" "target/cljs/testable.js"]}}) 28 | -------------------------------------------------------------------------------- /src/cljs/servant/core.cljs: -------------------------------------------------------------------------------- 1 | (ns servant.core 2 | (:require 3 | [cljs.core.async :refer [chan close! timeout put!]] 4 | [servant.worker :as worker]) 5 | (:require-macros [cljs.core.async.macros :as m :refer [go]] 6 | [servant.macros :refer [defservantfn]])) 7 | 8 | (defn webworker? [] 9 | (undefined? (.-document js/self))) 10 | 11 | (def not-webworker? (complement webworker?)) 12 | 13 | (defn spawn-servants 14 | "Returns a channel that lists available workers" 15 | [worker-count worker-script] 16 | (let [servant-channel (chan worker-count)] 17 | (go 18 | (doseq [x (range worker-count)] 19 | (>! servant-channel (js/Worker. worker-script)))) 20 | servant-channel)) 21 | 22 | (defn kill-servants 23 | "Kills worker-count # of workers" 24 | [servant-channel worker-count] 25 | (go 26 | (doseq [x (range worker-count)] 27 | (.terminate (key [f] 30 | (ffirst (filter #(= f (second %)) @worker/worker-fn-map))) 31 | 32 | (defn standard-message [worker fn-key args] 33 | (.postMessage worker (js-obj "command" "channel" "fn" fn-key "args" (clj->js args)))) 34 | 35 | (defn array-buffer-message 36 | "Post message by transferring context of the arraybuffers. 37 | The channel should be fed data like [[normal args] [arraybuffer1 arraybuffer2]]. 38 | Tells the worker to expect to return an arraybuffer" 39 | [worker fn-key args] 40 | (let [[args arraybuffers] args] 41 | (.postMessage worker (js-obj "command" "channel-arraybuffer" "fn" fn-key "args" (clj->js args)) (clj->js arraybuffers)))) 42 | 43 | (defn array-buffer-message-standard-reply 44 | "Post message by transferring context of the arraybuffers. 45 | The channel should be fed data like [[arg1 arg2] [arraybuffer1 arraybuffer2]]. 46 | Tells the worker to return normal data" 47 | [worker fn-key args] 48 | (let [[args arraybuffers] args] 49 | (.postMessage 50 | worker 51 | (js-obj "command" "channel" "fn" fn-key "args" (clj->js args)) 52 | (clj->js arraybuffers)))) 53 | 54 | (defn servant-thread-with-key [servant-channel post-message-fn fn-key & args] 55 | (let [ out-channel (chan 1) ] 56 | (go 57 | (let [worker (! out-channel (.-data %1)) 63 | ;; return the worker back to the servant-channel 64 | (>! servant-channel worker))))) 65 | out-channel)) 66 | 67 | (defn servant-thread [servant-channel post-message-fn f & args] 68 | (apply servant-thread-with-key servant-channel post-message-fn (f->key f) args)) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Servant 2 | 3 | A Clojurescript library for interacting with webworkers sanely 4 | 5 | ## Installation 6 | 7 | add 8 | [](https://clojars.org/servant) 9 | to your dependencies 10 | 11 | ## Usage 12 | 13 | Web workers are a pain to use, manage, and create, but they offer the ability of spawning real threads. 14 | This library seeks to give you the good parts, without any of the bad parts. 15 | 16 | Through the magic of core.async's channels, we can abstract out a lot of the implementation details in webworkers. 17 | And with some careful execution, we can run the web workers in a similar context. 18 | 19 | ### Spawning Servants 20 | We create all our web workers at once and keep them on hand in a pool of available workers. 21 | Any worker can run any function, so servants do not store any state. 22 | 23 | 24 | ```clojure 25 | (def worker-count 2) ;; how many servants we want in our servant-pool 26 | (def worker-script "/main.js") ;; This is whatever the name of the compiled javascript will be 27 | 28 | ;; We need to make sure that only the main script will spawn the servants. 29 | (when-not (servant/webworker?) 30 | ;; We keep all the servants in a buffered channel. 31 | (def servant-channel (servant/spawn-servants worker-count worker-script))) 32 | ``` 33 | 34 | ### Defining Functions 35 | We define functions that servants should be able to execute with `defservantfn`. It is crucial that these functions be pure. 36 | 37 | Under the hood, `defservantfn` creates a normal function, but it also tells the webworker to remember this function. 38 | 39 | ```clojure 40 | (defservantfn some-random-fn [a b] 41 | (+ a b)) 42 | ;; This can also call other functions within the scope! 43 | 44 | (defn make-it-funny [not-funny] 45 | (str "Hahahah:" not-funny)) 46 | 47 | (defservantfn servant-with-humor [your-joke] 48 | (make-it-funny your-joke)) 49 | ``` 50 | 51 | ### Using a servant 52 | To make a call you need to wire up your servant by providing the 53 | servant-channel, a message sending fn, and your defined servant fn. 54 | 55 | * Servant-channel: Allows the function to figure out to whom it should dispatch the work. 56 | * message-fn: Determines how the lowlevel `worker.postMessage` is called. The simplest is defined in servant/standard-message. 57 | * servant-fn: This is the defservantfn we created earlier 58 | * & args 59 | 60 | ```clojure 61 | (def result-channel (servant/servant-thread servant-channel servant/standard-message servant-fn 5 6)) 62 | ``` 63 | 64 | This will call the servant-fn using a servant from the pool with the args 5 6. 65 | This will return a channel that will contain the result. Similar to how core.async's thread works. 66 | 67 | ## Caveats 68 | Web workers have a completely separate context from the main running script, so to be able to call functions freely, 69 | we use the same file for the main browser thread, and web workers. But doing that comes at a cost, you have to 70 | prevent the webworker from running code meant for the browser. 71 | 72 | Our current solution is: 73 | ```clojure 74 | (defn window-load [] 75 | ;; your browser specific code here 76 | ) 77 | 78 | (if (servant/webworker?) 79 | (worker/bootstrap) ;; Run the setup code for the web worker 80 | (set! (.-onload js/window) window-load) ;; run the browser specific code 81 | ) 82 | ``` 83 | 84 | ## Separate Worker file 85 | 86 | Sometimes you want to have a separate cljs file for workers, and that's fine! 87 | Nothing in this library prevents you from that. 88 | 89 | Do the same thing you did before, but now this will run in a separate (possibly smaller) js file. 90 | You simply need to figure out what's the best way to pass data around the main browser context, 91 | which is significantly easier because they share the context! 92 | 93 | ## Examples 94 | [Simple example project](https://github.com/MarcoPolo/servant-demo) 95 | [Encrypt/Decrypt Project using webworkers](https://github.com/MarcoPolo/servant-crypt-demo) 96 | 97 | # Testing 98 | 99 | I can't seem to figure out a good way to test web workers :(, I'd love to hear some ideas! 100 | 101 | # TODO 102 | 103 | Write tests. 104 | 105 | ## License 106 | 107 | Copyright © 2013 Marco Munizaga 108 | 109 | Distributed under the Eclipse Public License, the same as Clojure. 110 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Eclipse Public License - v 1.0
31 | 32 |THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.
36 | 37 |1. DEFINITIONS
38 | 39 |"Contribution" means:
40 | 41 |a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and
43 |b) in the case of each subsequent Contributor:
44 |i) changes to the Program, and
45 |ii) additions to the Program;
46 |where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.
54 | 55 |"Contributor" means any person or entity that distributes 56 | the Program.
57 | 58 |"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.
61 | 62 |"Program" means the Contributions distributed in accordance 63 | with this Agreement.
64 | 65 |"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.
67 | 68 |2. GRANT OF RIGHTS
69 | 70 |a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.
76 | 77 |b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.
88 | 89 |c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.
101 | 102 |d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.
105 | 106 |3. REQUIREMENTS
107 | 108 |A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:
110 | 111 |a) it complies with the terms and conditions of this 112 | Agreement; and
113 | 114 |b) its license agreement:
115 | 116 |i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;
120 | 121 |ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;
124 | 125 |iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and
128 | 129 |iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.
133 | 134 |When the Program is made available in source code form:
135 | 136 |a) it must be made available under this Agreement; and
137 | 138 |b) a copy of this Agreement must be included with each 139 | copy of the Program.
140 | 141 |Contributors may not remove or alter any copyright notices contained 142 | within the Program.
143 | 144 |Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.
147 | 148 |4. COMMERCIAL DISTRIBUTION
149 | 150 |Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.
172 | 173 |For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.
183 | 184 |5. NO WARRANTY
185 | 186 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.
197 | 198 |6. DISCLAIMER OF LIABILITY
199 | 200 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 | 209 |7. GENERAL
210 | 211 |If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.
216 | 217 |If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.
223 | 224 |All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.
232 | 233 |Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.
252 | 253 |This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.
258 | 259 | 260 | 261 | --------------------------------------------------------------------------------