├── .travis.yml ├── LICENSE ├── README.md ├── doc └── intro.md ├── project.clj ├── src └── fun_utils │ ├── agent.clj │ ├── assocs.clj │ ├── cache.clj │ ├── core.clj │ ├── queue.clj │ └── threads.clj └── test └── fun_utils ├── agent_tests.clj ├── apply_get_create_tests.clj ├── buffered_chan_tests.clj ├── cache_tests.clj ├── chan_bridge_tests.clj ├── fixdelay_tests.clj ├── go_seq_tests.clj ├── merge_distinct_vects_tests.clj ├── queue_tests.clj └── star_channel_tests.clj /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | script: lein midje 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fun-utils 2 | 3 | Clojure utility functions that come up time and again while developing clojure software. 4 | 5 | 6 | ## Usage 7 | 8 | Best way to see how to use them is going through the unit tests. 9 | I've tried to test each utlity function in its own test file. 10 | 11 | [![Build Status](https://travis-ci.org/gerritjvv/fun-utils.svg)](https://travis-ci.org/gerritjvv/fun-utils) 12 | 13 | 14 | [![Clojars Project](http://clojars.org/fun-utils/latest-version.svg)](http://clojars.org/fun-utils) 15 | 16 | ## Queue 17 | 18 | The ```fun-utils.queue``` interface provides a basic protocol ```IQueue``` that encapsulates the offer, poll and size functions 19 | most commonly used with queues. It also provides an factory multi method to create different implementations. 20 | 21 | Wraps queues ArrayBlockingQueue from java.util.concurrent and SpmcArrayQueue, and MpmcArrayQueue from https://github.com/JCTools/JCTools 22 | 23 | ```clojure 24 | (require '[fun-utils.queue :as queue] :reload-all) 25 | 26 | ;;create a ArrayBlockingQueue 27 | (def q (queue/queue-factory :array-queue 10)) 28 | (queue/offer! q 10) 29 | (queue/size q) 30 | ;;1 31 | (queue/poll! q) 32 | ;;10 33 | 34 | ;;create a SpmcArrayQueue queue 35 | (def q (queue/queue-factory :spmc-array-queue 10)) 36 | (queue/offer! q 11) 37 | (queue/poll! q) 38 | ;;11 39 | 40 | ;;create a MpmcArrayQueue queue 41 | (def q (queue/queue-factory :mpmc-array-queue 10)) 42 | (queue/offer! q 12) 43 | (queue/poll! q) 44 | ;; 12 45 | 46 | ``` 47 | 48 | ## cache 49 | 50 | The ```fun-utils.cache``` namespace provides a Clojure Map wrapper arround the Google's Guava Cache. 51 | It gives us all of the perfromance and usibility of the Guava Cache builder but with the convinience of 52 | acting as a Clojure map. 53 | 54 | I've found that the guava cache with expire-on-write is much more reliable and performant than using Clojure's 55 | own ttl cache. 56 | 57 | ```clojure 58 | 59 | (require '[fun-utils.cache :as c]) 60 | 61 | (def cache (c/create-cache)) 62 | (assoc cache :a 1) 63 | (get cache :a) 64 | ;; 1 65 | 66 | (def cache-ttl (c/create-cache :expire-after-write 500)) 67 | (get cache-ttl :a) 68 | ;; nil 69 | (assoc cache-ttl :a 1) 70 | (get cache-ttl :a) 71 | ;; 1 72 | (Thread/sleep 1000) 73 | (get cache-ttl :a) 74 | ;; nil 75 | 76 | (def cache2 (c/create-loading-cache (fn [k] [k 1]))) 77 | (get cache2 1) 78 | ;; [1 1] 79 | 80 | ;;specialised memoize for single arity functions 81 | (def f (c/memoize-1 (fn [x] (Thread/sleep x) x))) 82 | (f 5000) 83 | ;; 5000 after waiting for 5 seconds 84 | 85 | (f 5000) 86 | ;; 5000 immediately 87 | 88 | (def f (c/memoize (fn [a v] (Thread/sleep v) v))) 89 | (f :a 5000) 90 | ;; 5000 after waiting for 5 seconds 91 | 92 | (f :a 5000) 93 | ;; 5000 immediately 94 | 95 | ;;;;;;;async background refresh of caches 96 | ;;;;;;; when using :refresh-after-write the guava cache will reload values after N milliseconds 97 | ;;;;;;; the load function is called using the default executor (same as for future Agent/soloExecutor) 98 | ;;;;;;; a custom java.util.concurrent.ExecutorService can be set on cache creation by setting to 99 | ;;;;;;; *executor* dynamic var, this variable is captured on cache creation 100 | ;;;;;;;see https://code.google.com/p/guava-libraries/wiki/CachesExplained#Refresh 101 | ;;;;;; https://github.com/gerritjvv/fun-utils/issues/1 102 | 103 | (import '[java.util.concurrent Executors]) 104 | (def exec (Executors/newFixedThreadPool 10)) 105 | 106 | (defn wait-and-print [[s ms]] 107 | (println "starting " s " at " (java.util.Date.)) 108 | (Thread/sleep ms) 109 | (println "finished " s " at " (java.util.Date.) " waited " ms "ms") 110 | [s ms (java.util.Date.)]) 111 | 112 | (def cache3 (binding [c/*executor* exec] (c/create-loading-cache wait-and-print :refresh-after-write 500))) 113 | ;; using the default executor 114 | ;;(def cache3 (c/create-loading-cache wait-and-print :refresh-after-write 500)) 115 | 116 | ;;wait one second 117 | (get cache3 ["foo1" 1000]) 118 | ;;return without waiting 119 | (get cache3 ["foo1" 1000]) 120 | 121 | ``` 122 | 123 | ## fixdelay and fixdelay-thread 124 | 125 | Use's clojure.core.async timeout to run an expression every n milliseconds. 126 | 127 | Example 128 | 129 | ```clojure 130 | 131 | (def d (fixdelay 1000 (prn "hi"))) 132 | ;; for IO code use fixdelay-thread 133 | ;; => Hi ... every second 134 | 135 | (stop-fixdelay d) 136 | ;;stops the fixdelay 137 | 138 | ``` 139 | 140 | 141 | ## go-seq and thread-seq 142 | 143 | Note: in all of the examples below go-seq can be changed for thread-seq, use thread-seq if you are running IO code. 144 | When writing go blocks there is a repeating pattern to avoid 100% cpu spin when the channel is closed. 145 | 146 | e.g. 147 | if we run the below code, the loop will spin without pause. 148 | 149 | ```clojure 150 | (require '[clojure.core.async :refer [go-loop chan !! ch1 i)) 229 | 230 | (prn (reduce + (repeatedly 5 #(!! ch1 i)) 237 | 238 | (prn (reduce + (repeatedly 5 #(> file-a (clojure.java.io/reader) (line-seq) (map #(Long/parseLong %)) sort) 331 | 332 | )) 333 | ;(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 334 | ; 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99) 335 | 336 | 337 | ``` 338 | 339 | ## Buffering and timeouts 340 | 341 | There are many times where you need to buffer up a series off results and then perform an operation on them, and if the count is not reached 342 | on a predefined timeout do the operation with the results collected. 343 | 344 | ```clojure 345 | 346 | (let [ch-source (chan) 347 | buff-ch (buffered-chan ch-source 10 1000 11)] 348 | 349 | (go 350 | (dotimes [i 100] 351 | (>! ch-source i))) 352 | 353 | 354 | (dotimes [i 10] 355 | (let [v (!! (.chan a) (fn [v] (apply f v args)))) 68 | a) 69 | 70 | (defn close-agent 71 | "Closes the mailbox for the agent 72 | if wait is true the agent will close and wait for the last function to process" 73 | [^BlockingAgent a & {:keys [wait] :or {wait false}}] 74 | (.set ^AtomicBoolean (.closed a) true) 75 | (async/close! (.chan a)) 76 | (when wait 77 | (.await ^CountDownLatch (.close-complete a)))) 78 | 79 | -------------------------------------------------------------------------------- /src/fun_utils/assocs.clj: -------------------------------------------------------------------------------- 1 | (ns 2 | ^{:doc "Performant functions unrolling get-in and assoc-in functions 3 | copied from http://blog.podsnap.com/tinhole.html"} 4 | fun-utils.assocs 5 | (:refer-clojure :exclude [get-in assoc-in])) 6 | 7 | 8 | (defmacro get-in 9 | "A macro for doing get in when the keys are known at compile time" 10 | [m path] 11 | (reduce (fn [acc k] (concat acc (list (if (vector? k) 12 | `(~(second k)) 13 | `(get ~k))))) 14 | `(-> ~m) path)) 15 | 16 | (defn- th-assoc-in-gen [m ks v] 17 | (let [k (first ks) 18 | ks (next ks)] 19 | (cond 20 | (vector? k) (let [[f-in f-out] k] 21 | (list f-in 22 | (if-not ks v (th-assoc-in-gen (list f-out m) ks v)))) 23 | ks (list 'assoc m k (th-assoc-in-gen (list 'get m k) ks v )) 24 | :else (list 'assoc m k v)))) 25 | 26 | (defmacro assoc-in 27 | "A macro for assoc-in when the keys are known at compile time" 28 | [m ks v] (th-assoc-in-gen m ks v)) -------------------------------------------------------------------------------- /src/fun_utils/cache.clj: -------------------------------------------------------------------------------- 1 | (ns 2 | fun-utils.cache 3 | (:import [com.google.common.cache CacheBuilder CacheLoader Cache LoadingCache] 4 | [com.google.common.util.concurrent SettableFuture] 5 | [java.util.concurrent TimeUnit Callable ExecutorService] 6 | (clojure.lang Agent)) 7 | (:require [potemkin.collections :refer [def-map-type]]) 8 | (:refer-clojure :exclude [memoize])) 9 | 10 | ;; Uses guava cache 11 | 12 | 13 | (def ^:dynamic ^ExecutorService *executor* Agent/soloExecutor) 14 | 15 | (defmacro submit [ service & body] 16 | `(let [f# (fn [] ~@body)] 17 | (.submit ^ExecutorService ~service ^Callable f#))) 18 | 19 | (defn ^CacheLoader cache-loader [f] 20 | (let [^ExecutorService exec *executor*] 21 | (proxy [CacheLoader] [] 22 | (load [k] (f k)) 23 | (reload [k oldvalue] 24 | (let [fut (SettableFuture/create)] 25 | (submit exec (.set fut (f k))) 26 | fut))))) 27 | 28 | (def-map-type GuavaLoadingCacheMap [^LoadingCache cache mta] 29 | (get [_ k default-value] 30 | (if-let [v (.get cache k)] v default-value)) 31 | (assoc [this k v] 32 | (.put cache k v) 33 | this) 34 | (dissoc [this k] 35 | (.invalidate cache k) 36 | this) 37 | (keys [_] 38 | (-> cache .asMap .keySet)) 39 | (meta [_] mta) 40 | (with-meta [_ mta] 41 | (GuavaLoadingCacheMap. cache mta))) 42 | 43 | (def-map-type GuavaCacheMap [^Cache cache mta] 44 | (get [_ k default-value] 45 | (if-let [v (.getIfPresent cache k)] v default-value)) 46 | (assoc [this k v] 47 | (.put cache k v) 48 | this) 49 | (dissoc [this k] 50 | (.invalidate cache k) 51 | this) 52 | (keys [_] 53 | (-> cache .asMap .keySet)) 54 | (meta [_] mta) 55 | (with-meta [_ mta] 56 | (GuavaCacheMap. cache mta))) 57 | 58 | (defn ^CacheBuilder -configure-cache 59 | [& {:keys [concurrency-level 60 | expire-after-access 61 | expire-after-write 62 | refresh-after-write 63 | soft-values 64 | weak-keys 65 | weak-values 66 | maximum-size 67 | time-unit] :or {^TimeUnit time-unit TimeUnit/MILLISECONDS}}] 68 | (let [^CacheBuilder builder (CacheBuilder/newBuilder)] 69 | (if maximum-size 70 | (.maximumSize builder (long maximum-size))) 71 | (if concurrency-level 72 | (.concurrencyLevel builder (int concurrency-level))) 73 | (if expire-after-access 74 | (.expireAfterAccess builder (long expire-after-access) time-unit)) 75 | (if expire-after-write 76 | (.expireAfterWrite builder (long expire-after-write) time-unit)) 77 | (if refresh-after-write 78 | (.refreshAfterWrite builder (long refresh-after-write) time-unit)) 79 | (if soft-values 80 | (.softValues builder)) 81 | (if weak-keys 82 | (.weakKeys builder)) 83 | (if weak-values 84 | (.weakValues builder)) 85 | builder)) 86 | 87 | 88 | (defn -create-loading-cache [loader-f & args] 89 | {:pre [(fn? loader-f)]} 90 | (.build ^CacheBuilder(apply -configure-cache args) (cache-loader loader-f))) 91 | 92 | (defn -create-cache [& args] 93 | (.build ^CacheBuilder(apply -configure-cache args))) 94 | 95 | (defn create-loading-cache 96 | "Background cache reloading is asynchronous and the loader-f function will be called 97 | in a background thread, the default thread pool is Agent/soloExecutor, and can be set using 98 | the dynamic variable *executor*" 99 | [loader-f & args] 100 | (:pre [(fn? loader-f)]) 101 | (GuavaLoadingCacheMap. (apply -create-loading-cache loader-f args) {})) 102 | 103 | (defn create-cache 104 | [& args] 105 | (GuavaCacheMap. (apply -create-cache args) {})) 106 | 107 | 108 | (defn memoize-1 109 | "Fast memoize for single arity functions backed by guava cache" 110 | [f & args] 111 | (let [c (apply create-loading-cache f args)] 112 | (fn [k] 113 | (get c k)))) 114 | 115 | (defn memoize 116 | "Memoize backed by guava cache" 117 | [f & args] 118 | (let [c (apply create-cache args)] 119 | (fn [k & args] 120 | (if-let [v (get c k)] 121 | v 122 | (when-let [v (apply f k args)] 123 | (assoc c k v) 124 | v))))) 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/fun_utils/core.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.core 2 | (:import (java.util.concurrent ExecutorService) 3 | (clojure.lang IFn) 4 | (java.util ArrayList) 5 | (java.util.concurrent.atomic AtomicReference AtomicLong AtomicBoolean)) 6 | (:require [clojure.core.async :refer [go ! !! alts! alts!! chan close! thread timeout go-loop]] 7 | [clojure.core.async :as async] 8 | [clojure.tools.logging :refer [info error]] 9 | [clj-tuple :refer [tuple]])) 10 | 11 | 12 | (defn chan-bridge 13 | "in a loop read from ch-source and write to ch-target 14 | this function returns inmediately and returns the ch-target" 15 | ([ch-source map-f ch-target] 16 | (chan-bridge (async/map map-f [ch-source]) ch-target)) 17 | ([ch-source ch-target] 18 | (go 19 | (while (not (Thread/interrupted)) 20 | (if-let [v (! ch-target v)))) 22 | ch-target)) 23 | 24 | (defn apply-get-create 25 | "Get a value from a map m using key k and apply the function f to the value 26 | f must have signuture to be applied as (apply f v args) 27 | m is returned, 28 | if the key does not exist in the map the value is created using (apply creawte-f args) 29 | and then applied to the function f, assoc'ed to the map and the new map is returned. 30 | 31 | This utility function is useful escecially when used with agents, refs, atoms and in loops" 32 | [m k f create-f & args] 33 | (if-let [v (get m k)] 34 | (do 35 | (apply f v args) 36 | m) 37 | (let [v (apply create-f args)] 38 | (apply f v args) 39 | (assoc m k v)))) 40 | 41 | 42 | (defn thread-seq2 43 | "Same as thread-seq except that the closed-f is called once the channel is closed and the last item is processed" 44 | ([f closed-f ch] 45 | (thread 46 | (try 47 | (loop [] 48 | (if-let [v (!! resp-ch (if v v []))) 119 | (.incrementAndGet status) 120 | (recur)) 121 | (.set status -100)))) 122 | 123 | [status ch])) 124 | close-channel (fn [ch-map key-val] 125 | (if-let [ch (get ch-map key-val)] 126 | (close! (second ch)))) 127 | 128 | apply-command (fn [command ch-map key-val] 129 | (cond 130 | (= command :remove) 131 | (do 132 | (close-channel ch-map key-val) 133 | (dissoc ch-map key-val)) 134 | :else 135 | ch-map)) 136 | 137 | star-channel-f (fn star-channel-f 138 | ([key-val f args] 139 | (star-channel-f wait-response key-val f args)) 140 | ([wait-response2 key-val f args] 141 | (if wait-response2 142 | (let [resp-ch (chan)] 143 | (>!! master-ch (tuple key-val resp-ch f args)) 144 | (!! master-ch (tuple key-val nil f args))))) 146 | close-f (fn [& args] 147 | (close! master-ch)) 148 | master-activity (AtomicLong. 0) 149 | master-exit (AtomicBoolean. false) 150 | 151 | ch-map-view (AtomicReference. nil)] 152 | (thread 153 | (loop [ch-map {}] 154 | ;some bug here in channels causes us to use !! (second ch) (tuple resp-ch f-n args)) 168 | (apply-command command ch-map key-val)) 169 | (do 170 | (>!! (second ch) (tuple resp-ch f args)) 171 | ch-map)) 172 | 173 | (let [ch (create-ch) 174 | ch-map3 (assoc ch-map key-val ch)] ;;this is the duplicate of above, but >! does not work behind functions :( 175 | (if (coll? f) 176 | (let [[command f-n] f] 177 | (>!! (second ch) (tuple resp-ch f-n args)) 178 | (apply-command command ch-map3 key-val)) 179 | (do 180 | (>!! (second ch) (tuple resp-ch f args)) 181 | ch-map3)))))] 182 | ;we use this for testing introspection and tooling to allow viewing what keys are in the star map 183 | ;but this is not an atom nor a ref its only a view 184 | (.set ^AtomicReference ch-map-view ch-map2) 185 | ch-map2) 186 | (catch Exception e (do (error e e) ch-map)))] 187 | (.incrementAndGet ^AtomicLong master-activity) 188 | (recur m)) 189 | (do 190 | (info "Closing star channel") 191 | (.set master-exit true) 192 | (doseq [[key-val ch] ch-map] 193 | (close! (second ch))))))) 194 | {:send star-channel-f :close close-f :ch-map-view ch-map-view :master-actitivy master-activity :master-exit master-exit})) 195 | 196 | 197 | (defn buffered-select 198 | "Creates a lazy sequence of messages for this datasource" 199 | [f-select init-pos] 200 | (letfn [(m-seq [buff pos] 201 | (let [buff2 (if (empty? buff) (f-select pos) buff)] 202 | (cons (first buff2) (lazy-seq (m-seq (rest buff2) (inc pos))))))] 203 | (m-seq nil init-pos))) 204 | 205 | (defn stop-fixdelay [ch] 206 | (close! ch)) 207 | 208 | (defmacro fixdelay 209 | "Runs the body every ms after the last appication of body completed" 210 | [ms & body] 211 | `(let [close-ch# (chan)] 212 | (go (loop [] 213 | (let [[v# ch#] (alts! [close-ch# (timeout ~ms)])] 214 | (if (not (= close-ch# ch#)) 215 | (do 216 | ~@body 217 | (recur)))))) 218 | close-ch#)) 219 | 220 | (defmacro fixdelay-thread 221 | "Runs the body every ms after the last appication of body completed, the code is run in a separate Thread 222 | Returns a channel that when passed to stop-fixdelay will close this thread" 223 | [ms & body] 224 | `(let [close-ch# (chan)] 225 | (thread 226 | (loop [] 227 | (let [[v# ch#] (alts!! [close-ch# (timeout ~ms)])] 228 | (if (not (= close-ch# ch#)) 229 | (do 230 | ~@body 231 | (recur)))))) 232 | close-ch#)) 233 | 234 | (defn submit 235 | "Helper function to type hint a function to a runnable, avoiding reflection 236 | when submitting to a thread" 237 | [^ExecutorService service ^IFn f] 238 | (let [^Runnable r f] 239 | (.submit service r))) 240 | 241 | 242 | (defn merge-distinct-vects 243 | " 244 | Merge the two vectors with only distinct elements remaining, 245 | such that (sort (merge-distinct-vects [1 2 5 6 3] [1 2 8 9])) => [1 2 3 5 6 8 9] 246 | " 247 | [v1 v2] 248 | (if (empty? v2) 249 | v1 250 | (-> (apply conj v1 v2) set vec))) 251 | 252 | (defn always-false 253 | ([] (tuple false nil)) 254 | ([prev v] (tuple false nil))) 255 | 256 | (defn aconj! [^ArrayList a v] 257 | (.add a v) 258 | a) 259 | 260 | (defn asize [^ArrayList a] (.size a)) 261 | (defn aclear! [^ArrayList a] (.clear a) a) 262 | 263 | (defn buffered-chan 264 | "Reads from ch-source and if either timeout or the buffer-count has been 265 | read the result it sent to the channel thats returned from this function 266 | check-f must be callable with zero and two arguments and returns a tuple of [boolean-val state] 267 | the state variable will be given to check-f when its called next together with the vector buffer, 268 | this allows check-f to accumulate counts etc, see always-false 269 | " 270 | ([ch-source buffer-count timeout-ms] 271 | (buffered-chan ch-source buffer-count timeout-ms 1)) 272 | ([ch-source buffer-count timeout-ms buffer-or-n] 273 | (buffered-chan ch-source buffer-count timeout-ms buffer-or-n always-false)) 274 | ([ch-source buffer-count timeout-ms buffer-or-n check-f] 275 | 276 | (let [ch-target (chan buffer-or-n)] 277 | (thread 278 | (loop [buff (ArrayList.) t (timeout timeout-ms) prev-v (check-f)] 279 | (let [[v ch] (alts!! (tuple ch-source t)) 280 | b (if v (aconj! buff v) buff)] 281 | (if (and (not (= ch t)) (nil? v)) 282 | (if (> (asize b) 0) ;exit loop 283 | (>!! ch-target (vec b))) 284 | (let [[break? prev-2] (check-f prev-v b) 285 | [b2 t2] (if (or (>= (asize b) buffer-count) (not v) break?) 286 | (do 287 | (if (> (asize b) 0) 288 | (>!! ch-target (vec b))) ;send the buffer to the channel 289 | (tuple (aclear! b) (timeout timeout-ms))) 290 | (tuple b t))] 291 | ;create a new buffer and new timeout 292 | (recur b2 t2 prev-2)) ;pass the new buffer and the current timeout 293 | )))) 294 | ch-target))) 295 | 296 | 297 | -------------------------------------------------------------------------------- /src/fun_utils/queue.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.queue 2 | (:import 3 | [java.util.concurrent ArrayBlockingQueue TimeUnit] 4 | [org.jctools.queues SpmcArrayQueue MpmcArrayQueue] 5 | (java.util ArrayList List))) 6 | 7 | 8 | (defprotocol IQueue 9 | (-offer! [this data] [this data timeout-ms]) 10 | (-poll! [this] [this timeout-ms]) 11 | (-drain! [this n]) 12 | (-size [this] "Size of the queue")) 13 | 14 | (defn- _take! 15 | " 16 | Helper function to simulate blocking for queues that do not directly support it 17 | Will block the current thread till a value is available" 18 | [queue ^long timeout-ms] 19 | (if-let [v (-poll! queue)] 20 | v 21 | (let [start-wait-time (System/currentTimeMillis)] 22 | (loop [v1 (-poll! queue)] 23 | (if v1 24 | v1 25 | (when (< (- (System/currentTimeMillis) start-wait-time) timeout-ms) 26 | (Thread/yield) 27 | (recur (-poll! queue)))))))) 28 | 29 | (defn- _put! 30 | "Helper function to simulate blocking for queues that do not directly support it 31 | Will block the current thread till a value can be placed on the queue" 32 | [queue data ^long timeout-ms] 33 | (if (-offer! queue data) 34 | true 35 | (let [start-wait-time (System/currentTimeMillis)] 36 | (loop [v1 (-offer! queue data)] 37 | (if v1 38 | true 39 | (when (< (- (System/currentTimeMillis) start-wait-time) timeout-ms) 40 | (Thread/yield) 41 | (recur (-offer! queue data)))))))) 42 | 43 | (defn offer! 44 | "Non blocking put on queue, if successfull return true, or wait timeout-ms for a value to appear" 45 | ([queue data] (-offer! queue data)) 46 | ([queue data timeout-ms] (-offer! queue data timeout-ms))) 47 | (defn poll! 48 | "Non blocking get on queue, if no value returns nil, or wait timeout-ms for the action to complete" 49 | ([queue] (-poll! queue)) 50 | ([queue timeout-ms] (-poll! queue timeout-ms))) 51 | 52 | (defn size [queue] (-size queue)) 53 | 54 | (defmulti queue-factory (fn [t limit & args] t)) 55 | 56 | (defmethod queue-factory :array-queue [_ limit & _] 57 | (let [^ArrayBlockingQueue queue (ArrayBlockingQueue. (int limit))] 58 | (reify IQueue 59 | (-offer! [_ data timeout-ms] (.offer queue data timeout-ms TimeUnit/MILLISECONDS)) 60 | (-offer! [_ data] (.offer queue data)) 61 | (-drain! [_ n] (let [^List l (ArrayList. (int n))] 62 | (.drainTo queue l (int n)) 63 | l)) 64 | (-poll! [_ timeout-ms] (.poll queue timeout-ms TimeUnit/MILLISECONDS)) 65 | (-poll! [_] (.poll queue)) 66 | (-size [_] (.size queue))))) 67 | 68 | (defmethod queue-factory :spmc-array-queue [_ limit & _] 69 | (let [^SpmcArrayQueue queue (SpmcArrayQueue. (int limit))] 70 | (reify IQueue 71 | (-offer! [this data timeout-ms] (_put! this data timeout-ms)) 72 | (-offer! [_ data] (.offer queue data)) 73 | (-drain! [_ n] (if-let [o (.poll queue)] 74 | [o] 75 | [])) 76 | (-poll! [this timeout-ms] (_take! this timeout-ms)) 77 | (-poll! [_] (.poll queue)) 78 | (-size [_] (.size queue))))) 79 | 80 | (defmethod queue-factory :mpmc-array-queue [_ limit & _] 81 | (let [^MpmcArrayQueue queue (MpmcArrayQueue. (int limit))] 82 | (reify IQueue 83 | (-offer! [this data timeout-ms] (_put! this data timeout-ms)) 84 | (-offer! [_ data] (.offer queue data)) 85 | (-drain! [_ n] (if-let [o (.poll queue)] 86 | [o] 87 | [])) 88 | (-poll! [this timeout-ms] (_take! this timeout-ms)) 89 | (-poll! [_] (.poll queue)) 90 | (-size [_] (.size queue))))) 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/fun_utils/threads.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.threads 2 | (:require [clojure.tools.logging :refer [error info]] 3 | [clojure.core.async :refer [alts!!]]) 4 | (:import (java.util.concurrent Executors ArrayBlockingQueue ThreadPoolExecutor ExecutorService ThreadPoolExecutor$CallerRunsPolicy TimeUnit) 5 | (java.util.concurrent.atomic AtomicBoolean))) 6 | 7 | 8 | ;; A package that allows listening on channels using real threads and only react on events 9 | ;; in a shared pool of threads. 10 | 11 | 12 | (defn ^ExecutorService create-exec-service [threads] 13 | (let [queue (ArrayBlockingQueue. (int 10)) 14 | exec (doto (ThreadPoolExecutor. 2 (if (< threads 2) 2 threads) 10 TimeUnit/SECONDS queue) 15 | (.setRejectedExecutionHandler (ThreadPoolExecutor$CallerRunsPolicy.)))] 16 | exec)) 17 | 18 | (defn submit [^ExecutorService service ^Runnable r] 19 | (.submit service r)) 20 | 21 | (defn send-to-fun [service ch v fun-map] 22 | (when-let [f (get fun-map ch)] 23 | (submit service (fn [] 24 | (try 25 | (f v) 26 | (catch Exception e (error e e))))))) 27 | 28 | (defn listen [{:keys [chans-ref fun-map-ref]} ch f] 29 | {:pre [(fn? f) chans-ref fun-map-ref]} 30 | (dosync 31 | (alter fun-map-ref assoc ch f) 32 | (alter chans-ref (fn [v] 33 | (vec (set (conj v ch)))))) 34 | ch) 35 | 36 | (defn stop-listen 37 | "Remember to close ch if you are not going to use it" 38 | [{:keys [chans-ref fun-map-ref]} ch] 39 | {:pre [chans-ref fun-map-ref ch]} 40 | (dosync 41 | (alter fun-map-ref dissoc ch) 42 | (alter chans-ref (fn [v] 43 | (-> v set (disj ch) vec)))) 44 | ch) 45 | 46 | (defn close! [{:keys [^ExecutorService executor]} & {:keys [timeout-ms] :or {timeout-ms 2000}}] 47 | (.shutdown executor) 48 | (if-not (.awaitTermination executor (long timeout-ms) TimeUnit/MILLISECONDS) 49 | (.shutdownNow executor))) 50 | 51 | (defn shared-threads [threads] 52 | {:pre [(> threads 0)]} 53 | (let [executor (create-exec-service (inc threads)) 54 | chans-ref (ref []) 55 | fun-map-ref (ref {}) 56 | ctx {:executor executor :chans-ref chans-ref :fun-map-ref fun-map-ref} 57 | stopped (AtomicBoolean. true)] 58 | (submit executor 59 | (fn [] 60 | (while (not (.isInterrupted (Thread/currentThread))) 61 | (try 62 | (let [chans @chans-ref] 63 | (if-not (empty? chans) 64 | (do 65 | (let [[v ch] (alts!! chans)] 66 | (if v 67 | (send-to-fun executor ch v @fun-map-ref) 68 | (stop-listen ctx ch)))) 69 | (Thread/sleep 500))) 70 | (catch InterruptedException e nil) 71 | (catch Exception e (error e e)))) 72 | (info "<<<<<<<<<<<<<<<<<<<<< Exiting shared-threads loop >>>>>>>>>>>>>>>>>>>>>>"))) 73 | ctx)) 74 | 75 | -------------------------------------------------------------------------------- /test/fun_utils/agent_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.agent-tests 2 | (:require [fun-utils.agent :as agnt]) 3 | (:use midje.sweet)) 4 | 5 | 6 | (fact "Test blocking agent no blocking" 7 | (let [a-map (agnt/agent {})] 8 | (dotimes [i 1000] (agnt/send a-map assoc-in [:a :b] 1)) 9 | (Thread/sleep 1000) 10 | @a-map => {:a {:b 1}})) 11 | 12 | 13 | (fact "Test blocking agent blocking" 14 | (let [a-map (agnt/agent {}) 15 | blocking (fn [& _] (Thread/sleep 2000))] 16 | (try 17 | (do 18 | (agnt/send a-map blocking) => truthy 19 | 20 | (agnt/send a-map blocking) => truthy 21 | 22 | (agnt/send a-map blocking :timeout 100) => truthy) 23 | (finally 24 | (agnt/close-agent a-map))))) 25 | -------------------------------------------------------------------------------- /test/fun_utils/apply_get_create_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.apply-get-create-tests 2 | (:require [fun-utils.core :refer [apply-get-create]]) 3 | (:use midje.sweet)) 4 | 5 | 6 | (facts "Test apply-get-create" 7 | 8 | (fact "test new value created" 9 | (apply-get-create {} :a inc (fn [& args] 1)) => {:a 1} 10 | 11 | ;the inc is applied but the result is not assoced to the map 12 | ;this method is used to creawte resources in a map if they do not exist 13 | ;e.g. agents, channels, files 14 | (apply-get-create {:a 1} :a inc (fn [& args] 1)) => {:a 1} 15 | ) 16 | ;(fact "test with ref" 17 | ; (let [v (ref {})] 18 | ; (dosync 19 | ; (alter v apply-get-create :a (fn [agnt] (send agnt inc)) (fn [& args] (agent 1))) 20 | ; (alter v apply-get-create :a (fn [agnt] (send agnt inc)) (fn [& args] (agent 1))) 21 | ; (alter v apply-get-create :a (fn [agnt] (send agnt inc)) (fn [& args] (agent 1)))) 22 | ; 23 | ; (prn @v) 24 | ; (deref (:a @v)) => 1 25 | ; 26 | ; )) 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /test/fun_utils/buffered_chan_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.buffered-chan-tests 2 | (:require [fun-utils.core :refer [buffered-chan]] 3 | [clojure.core.async :refer [>!! ! ! ch-source i))) 16 | 17 | 18 | (dotimes [i 10] 19 | (let [v ( (map (fn [x] (+ (* i 10) x)) (range 10)))) 21 | 22 | )) 23 | (fact "Test timeout" 24 | (let [ch-source (chan) 25 | buff-ch (buffered-chan ch-source 10 500)] 26 | (>!! ch-source 1) 27 | (Thread/sleep 600) 28 | ( [1] 29 | 30 | )) 31 | 32 | (fact "Test using custom function" 33 | (let [ch-source (chan) 34 | buff-ch (buffered-chan ch-source 10 10000 10 (fn 35 | ([] [false nil]) 36 | ([acc v] [(= v [1 2]) nil])))] 37 | (>!! ch-source 1) 38 | (>!! ch-source 2) 39 | 40 | (Thread/sleep 200) 41 | ( [1 2] 42 | 43 | ))) 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/fun_utils/cache_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.cache-tests 2 | (:require [fun-utils.cache :as c]) 3 | (:use midje.sweet)) 4 | 5 | 6 | (facts "Test cache implementations" 7 | 8 | (fact "Test Loader cache" 9 | (let [cache (c/create-cache)] 10 | (get cache 1) => nil 11 | (get cache 1 2) => 2 12 | (assoc cache 1 :a) 13 | (get cache 1) => :a 14 | (keys cache) => '(1) 15 | (vals cache) => '(:a) 16 | (dissoc cache 1) 17 | (get cache 1) => nil)) 18 | 19 | (fact "Test Loading cache" 20 | (let [cache (c/create-loading-cache (fn [k] [k 1]))] 21 | (get cache 1) => [1 1] 22 | (get cache 2) => [2 1])) 23 | 24 | (fact "Test Expire cache" 25 | (let [cache (c/create-cache :expire-after-write 400)] 26 | (get cache 1) => nil 27 | (assoc cache 1 :a) 28 | (get cache 1) => :a 29 | (Thread/sleep 1500) 30 | (get cache 1) => nil))) 31 | -------------------------------------------------------------------------------- /test/fun_utils/chan_bridge_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.chan-bridge-tests 2 | (:require [fun-utils.core :refer [chan-bridge]] 3 | [clojure.core.async :refer [chan >!! !! ch1 i)) 15 | 16 | (reduce + (repeatedly 5 #( 10)) 17 | 18 | (fact "mapped bridge" 19 | (let [ch1 (chan 10) 20 | ch2 (chan-bridge ch1 inc (chan 10))] 21 | 22 | (doseq [i (range 5)] (>!! ch1 i)) 23 | 24 | ;(prn (repeatedly 5 #( 15 26 | 27 | ))) 28 | -------------------------------------------------------------------------------- /test/fun_utils/fixdelay_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.fixdelay-tests 2 | (:require [fun-utils.core :refer [fixdelay]]) 3 | (:import [java.util.concurrent.atomic AtomicInteger]) 4 | (:use midje.sweet)) 5 | 6 | (facts "Test fixdelay" 7 | 8 | (fact "Test fix delay runs" 9 | (let [counter (AtomicInteger.)] 10 | (fixdelay 100 (.incrementAndGet counter)) 11 | (Thread/sleep 500) 12 | (> (.get counter) 2) => true))) 13 | -------------------------------------------------------------------------------- /test/fun_utils/go_seq_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.go-seq-tests 2 | (:require [fun-utils.core :refer [go-seq]] 3 | [clojure.core.async :refer [chan >!! !! ch 1) (Thread/sleep 200) 18 | (.get reg) => 1 19 | (>!! ch 2) (Thread/sleep 200) 20 | (.get reg) => 2 21 | 22 | (close! ch) 23 | (>!! ch 3) (Thread/sleep 200) 24 | (.get reg) => 2 ;the loop has terminated, the value will not be set 25 | 26 | )) 27 | (fact "Test go seq f ch & chs" 28 | 29 | (let [ch1 (chan 10) 30 | ch2 (chan 10) 31 | ch3 (chan 10) 32 | 33 | reg (AtomicReference. false)] 34 | 35 | (go-seq (fn [v ch] 36 | (.set reg [v ch]) 37 | (not (= ch3 ch))) ch1 ch2 ch3) 38 | 39 | (>!! ch1 1) (Thread/sleep 200) 40 | (.get reg) => [1 ch1] 41 | (>!! ch2 2) (Thread/sleep 200) 42 | (.get reg) => [2 ch2] 43 | (>!! ch3 3) (Thread/sleep 200) 44 | (.get reg) => [3 ch3] 45 | 46 | 47 | (>!! ch1 4) (Thread/sleep 200) 48 | (.get reg) => [3 ch3] ;the loop has terminated, the value will not be set 49 | 50 | ))) 51 | 52 | -------------------------------------------------------------------------------- /test/fun_utils/merge_distinct_vects_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.merge-distinct-vects-tests 2 | (:require [fun-utils.core :refer [merge-distinct-vects]]) 3 | (:use midje.sweet)) 4 | 5 | (facts "Test merging distinct vects" 6 | 7 | (fact "Test merge-distinct-vects" 8 | (sort (merge-distinct-vects [1 2 5 6 3] [1 2 8 9])) => [1 2 3 5 6 8 9] 9 | )) 10 | -------------------------------------------------------------------------------- /test/fun_utils/queue_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.queue-tests 2 | (:require [fun-utils.queue :as queue]) 3 | (:use midje.sweet)) 4 | 5 | (fact "Test Different Queue implementations" 6 | 7 | (defn test-queue [t] 8 | (let [q (queue/queue-factory t 2) 9 | q2 (queue/queue-factory t 100)] 10 | (queue/offer! q 10) => true 11 | (queue/offer! q 10) => true 12 | (queue/offer! q 10 500) => falsey 13 | (queue/size q) => 2 14 | 15 | (queue/poll! q) => 10 16 | (queue/poll! q 1000) => 10 17 | (queue/poll! q 500) => falsey 18 | 19 | (dotimes [i 10] 20 | (queue/offer! q2 i)) 21 | 22 | (queue/-drain! q2 3) => (some-checker #(= % [0 1 2]) #(= % [0])))) 23 | 24 | (test-queue :array-queue) 25 | (test-queue :spmc-array-queue) 26 | (test-queue :mpmc-array-queue)) -------------------------------------------------------------------------------- /test/fun_utils/star_channel_tests.clj: -------------------------------------------------------------------------------- 1 | (ns fun-utils.star-channel-tests 2 | (:require [fun-utils.core :refer [star-channel submit]]) 3 | (:import [java.io File] 4 | [java.util.concurrent Executors TimeUnit] 5 | (java.util.concurrent.atomic AtomicInteger)) 6 | (:use midje.sweet)) 7 | 8 | 9 | (facts "Test star channel" 10 | (comment 11 | (fact "Check that different keys are created" 12 | ;star-channel [& {:keys [master-buff buff] :or {master-buff 100 buff 100}}] 13 | (let [{:keys [send close]} (star-channel)] 14 | (send :a inc 1) => 2 15 | (send :a inc 2) => 3 16 | (send :b inc 1) => 2 17 | 18 | ))) 19 | (fact "Check key removal" 20 | (let [star (star-channel :wait-response true) 21 | send (:send star) 22 | call-count (AtomicInteger. 0) 23 | map-view (fn [] (-> star :ch-map-view (.get)))] 24 | (send "abc1" (fn [f] (.incrementAndGet call-count)) nil) 25 | (send "abc2" (fn [f] (.incrementAndGet call-count)) nil) 26 | 27 | (send "abc1" [:remove (fn [f] (.incrementAndGet call-count))] nil) 28 | (-> (map-view) keys) => ["abc2"] 29 | (.get call-count) => 3)) 30 | (fact "Check concurrency" 31 | ;star-channel [& {:keys [master-buff buff] :or {master-buff 100 buff 100}}] 32 | (let [ 33 | base-dir (doto (File. "target/tests/star-channel-tests/concurrent") 34 | (.mkdirs)) 35 | {:keys [send close]} (star-channel :wait-response true) ;we set wait-response to true, because we want the items to be written in order 36 | file-a (doto (File. base-dir "file-a") (.delete) (.createNewFile)) 37 | file-b (doto (File. base-dir "file-b") (.delete) (.createNewFile)) 38 | exec (Executors/newCachedThreadPool)] 39 | 40 | (dotimes [i 100] 41 | (submit exec #(send :a (fn [f] (spit f (str i "\n") :append true)) file-a))) 42 | 43 | (dotimes [i 100] 44 | (submit exec #(send :b (fn [f] (spit f (str i "\n") :append true)) file-b))) 45 | 46 | ;wait for threads 47 | (doto exec 48 | (.shutdown) 49 | (.awaitTermination 10 TimeUnit/SECONDS)) 50 | 51 | ;check file contents 52 | (with-open [rdr (clojure.java.io/reader file-a)] 53 | (loop [i 0 lines (sort (map #(Long/parseLong %) (line-seq rdr)))] 54 | (if-let [line (first lines)] 55 | (do 56 | line => (long i) 57 | (recur (inc i) (rest lines)))))) 58 | 59 | (with-open [rdr (clojure.java.io/reader file-b)] 60 | (loop [i 0 lines (sort (map #(Long/parseLong %) (line-seq rdr)))] 61 | (if-let [line (first lines)] 62 | (do 63 | line => (long i) 64 | (recur (inc i) (rest lines)))))) 65 | 66 | ))) 67 | 68 | 69 | --------------------------------------------------------------------------------