   [f]
   (swap! global-handlers disj f))
 
+(defn- run-message-handler
+  "Invokes a registered MIDI message handler function with an incoming
+  message that has been identified as appropriate to send to it. If an
+  exception is thrown during that invocation, responds appropriately,
+  including shutting down the message handler thread if it was
+  interrupted. This is done by setting the `running` atom to `false`."
+  [handler msg running]
+  (try
+    ;; TODO: This is not really safe because if the handler blocks it ties up all future
+    ;;       MIDI dispatch. Should do in a future with a timeout? Don't want to use a million
+    ;;       threads for this either, so a core.async channel approach is probably best.
+    ;;
+    (handler msg)
+    (catch InterruptedException e
+      (timbre/info "MIDI event handler thread interrupted, shutting down.")
+      (reset! running false)
+      (reset! midi-transfer-thread nil))
+    (catch Throwable t
+      (timbre/error t "Problem running MIDI event handler."))))
 
 (defn- clock-message-handler
   "Invoked whenever any midi input device being managed receives a
   clock message. Checks whether there are any metronomes being synced
   to that device, and if so, passes along the event."
-  [msg]
+  [msg running]
   (doseq [handler (vals (get @synced-metronomes (@midi-device-key (:device msg))))]
-    (handler msg)))
+    (when @running
+      (run-message-handler handler msg running))))
 
 (defn- cc-message-handler
   "Invoked whenever any midi input device being managed receives a
   control change message. Checks whether there are any handlers (such
   as for launching cues or mapping show variables) attached to it, and
   if so, calls them."
-  [msg]
+  [msg running]
   (doseq [handler (get-in @control-mappings [(@midi-device-key (:device msg)) (:channel msg) (:note msg)])]
-    (handler msg)))
+    (when @running
+      (run-message-handler handler msg running))))
 
 (defn- note-message-handler
   "Invoked whenever any midi input device being managed receives a
   note message. Checks whether there are any handlers (such as for
   launching cues or mapping show variables) attached to it, and if so,
   calls them."
-  [msg]
+  [msg running]
   (doseq [handler (get-in @note-mappings [(@midi-device-key (:device msg)) (:channel msg) (:note msg)])]
-    (handler msg)))
+    (when @running
+      (run-message-handler handler msg running))))
 
 (defn- aftertouch-message-handler
   "Invoked whenever any midi input device being managed receives an
   aftertouch (polyphonic key pressure) message. Checks whether there
   are any handlers attached to it, and if so, calls them."
-  [msg]
+  [msg running]
   (doseq [handler (get-in @aftertouch-mappings [(@midi-device-key (:device msg)) (:channel msg) (:note msg)])]
-    (handler msg)))
+    (when @running
+      (run-message-handler handler msg running))))
 
 (defn- sysex-message-handler
   "Invoked whenever any midi input device being managed receives a
   System Exclusive message. Checks whether there are any handlers
   attached to it, and if so, calls them."
-  [msg]
+  [msg running]
   (doseq [handler (get-in @sysex-mappings [(@midi-device-key (:device msg))])]
-    (handler msg)))
+    (when @running
+      (run-message-handler handler msg running))))
 
 (defonce
   ^{:private true

  {:pre [(= (type device) :midi-device) (fn? f)]}
  (swap! disconnected-device-handlers #(update-in % [(@midi-device-key device)] disj f)))

(defonce ^{:private true
           :doc "The metronomes which are being synced to MIDI clock
  pulses. A map whose keys are the `javax.sound.midi.MidiDevice` on
  which clock pulses are being received, and whose values are in turn
  maps whose keys are the metronomes being synced to pulses from that
  device, and whose values are the sync function to call when each
  pulse is received."} synced-metronomes (atom {}))

(defonce ^{:private true
           :doc "Functions to be called when MIDI Controller Change
  messages arrive from particular input ports. A set of nested maps
  whose keys are the `javax.sound.midi.MidiDevice` on which the
  message should be watched for, the channel to watch, and the
  controller number to watch for. The values are sets of functions to
  be called with each matching message."}
  control-mappings (atom {}))

(defonce ^{:private true
           :doc "Functions to be called when MIDI Note messages arrive
  from particular input ports. A set of nested maps whose keys are the
  `javax.sound.midi.MidiDevice` on which the message should be watched
  for, the channel to watch, and the note number to watch for. The
  values are sets of functions to be called with each matching
  message."}

    (when (= (:status msg) :timing-clock)
      (swap! clock-sources assoc-in [device-key :timing-clock] (now)))
    (check-for-traktor-beat-phase msg device-key)))

(defn mac?
  "Return true if we seem to be running on a Mac."
