Permalink
Browse files

NB: fix localization formatter thread safety

  • Loading branch information...
1 parent 1dc25ba commit fe24fc43b6ca2868a8c495d45aef5a9948d37658 @ptaoussanis committed Mar 7, 2014
Showing with 46 additions and 13 deletions.
  1. +5 −1 CHANGELOG.md
  2. +1 −1 project.clj
  3. +32 −10 src/taoensso/tower.clj
  4. +8 −1 src/taoensso/tower/utils.clj
View
@@ -33,6 +33,10 @@
* Dropped (experimental) `:scope-var` tconfig option.
* Dropped (experimental) `:root-scope` tconfig option.
+### Fixes
+
+ * All localization formatters are now correctly thread safe.
+
## v2.0.2 / 2014 Jan 19
@@ -107,4 +111,4 @@ So, basically, idiomatic Tower usage has been simplified:
## v1.5.1 → v1.6.0
* A number of bug fixes.
- * Added support for translation aliases. If a dictionary entry's value is a keyword, it will now function as a pointer to another entry's value. See the default dictionary for an example.
+ * Added support for translation aliases. If a dictionary entry's value is a keyword, it will now function as a pointer to another entry's value. See the default dictionary for an example.
View
@@ -11,7 +11,7 @@
*assert* true}
:dependencies
[[org.clojure/clojure "1.4.0"]
- [com.taoensso/encore "0.9.2"]
+ [com.taoensso/encore "0.9.6"]
[com.taoensso/timbre "3.1.1"]
[markdown-clj "0.9.35"]]
View
@@ -6,7 +6,7 @@
[clojure.java.io :as io]
[taoensso.encore :as encore]
[taoensso.timbre :as timbre]
- [taoensso.tower.utils :as utils :refer (defmem-)])
+ [taoensso.tower.utils :as utils :refer (defmem- defmem-*)])
(:import [java.util Date Locale TimeZone Formatter]
[java.text Collator NumberFormat DateFormat]))
@@ -46,7 +46,17 @@
(time (dotimes [_ 10000] (locale :en))))
;;;; Localization
-
+;; The Java date API is a mess, but we (thankfully!) don't need much of it for
+;; the simple date formatting+parsing that Tower provides. So while the
+;; java.time (Java 8) and Joda-Time APIs are better, we choose instead to just
+;; use the widely-available date API and patch over the relevant nasty bits.
+;; This is an implementation detail from the perspective of lib consumers.
+;;
+;; Note that we use DateFormat rather than SimpleDateFormat since it offers
+;; better facilities (esp. wider locale support, etc.).
+
+;; Unlike SimpleDateFormat (with it's arbitrary patterns), DateFormat supports
+;; a limited set of predefined locale-specific styles:
(def ^:private ^:const dt-styles
{:default DateFormat/DEFAULT
:short DateFormat/SHORT
@@ -63,14 +73,26 @@
st2 (dt-styles (or len2 len1 :default))]
[type st1 st2]))))
-(defmem- f-date DateFormat [Loc st] (DateFormat/getDateInstance st Loc))
-(defmem- f-time DateFormat [Loc st] (DateFormat/getTimeInstance st Loc))
-(defmem- f-dt DateFormat [Loc ds ts] (DateFormat/getDateTimeInstance ds ts Loc))
-
-(defmem- f-number NumberFormat [Loc] (NumberFormat/getNumberInstance Loc))
-(defmem- f-integer NumberFormat [Loc] (NumberFormat/getIntegerInstance Loc))
-(defmem- f-percent NumberFormat [Loc] (NumberFormat/getPercentInstance Loc))
-(defmem- f-currency NumberFormat [Loc] (NumberFormat/getCurrencyInstance Loc))
+;;; Some contortions here to get high performance thread-safe formatters (none
+;;; of the java.text formatters are thread-safe!). Each constructor* call will
+;;; return a memoized (=> shared) proxy, that'll return thread-local instances
+;;; on `.get`.
+;;
+(defmem-* f-date* [Loc st] (DateFormat/getDateInstance st Loc)) ; proxy
+(defn- f-date ^DateFormat [Loc st] (.get (f-date* Loc st)))
+(defmem-* f-time* [Loc st] (DateFormat/getTimeInstance st Loc)) ; proxy
+(defn- f-time ^DateFormat [Loc st] (.get (f-time* Loc st)))
+(defmem-* f-dt* [Loc ds ts] (DateFormat/getDateTimeInstance ds ts Loc)) ; proxy
+(defn- f-dt ^DateFormat [Loc ds ts] (.get (f-dt* Loc ds ts)))
+;;
+(defmem-* f-number* [Loc] (NumberFormat/getNumberInstance Loc)) ; proxy
+(defn- f-number ^NumberFormat [Loc] (.get (f-number* Loc)))
+(defmem-* f-integer* [Loc] (NumberFormat/getIntegerInstance Loc)) ; proxy
+(defn- f-integer ^NumberFormat [Loc] (.get (f-integer* Loc)))
+(defmem-* f-percent* [Loc] (NumberFormat/getPercentInstance Loc)) ; proxy
+(defn- f-percent ^NumberFormat [Loc] (.get (f-percent* Loc)))
+(defmem-* f-currency* [Loc] (NumberFormat/getCurrencyInstance Loc)) ; proxy
+(defn- f-currency ^NumberFormat [Loc] (.get (f-currency* Loc)))
(defprotocol IFmt (pfmt [x loc style]))
(extend-protocol IFmt
@@ -1,7 +1,8 @@
(ns taoensso.tower.utils
{:author "Peter Taoussanis"}
(:require [clojure.string :as str]
- [markdown.core]))
+ [markdown.core]
+ [taoensso.encore :as encore]))
(defn leaf-nodes
"Takes a nested map and squashes it into a sequence of paths to leaf nodes.
@@ -46,6 +47,12 @@
~(with-meta '[& args] {:tag type-hint})
(apply memfn# ~'args))))
+(defmacro defmem-*
+ "Like `defmem-` but wraps body with `thread-local-proxy`."
+ [name fn-params fn-body]
+ `(defmem- ~name ThreadLocal ~fn-params
+ (encore/thread-local-proxy ~fn-body)))
+
(defn parse-http-accept-header
"Parses HTTP Accept header and returns sequence of [choice weight] pairs
sorted by weight."

0 comments on commit fe24fc4

Please sign in to comment.