Bläddra i källkod

fix: thread-api calls before shared-service inited

chore: better fns naming
enhance: add do-not-wait
rcmerci 6 månader sedan
förälder
incheckning
254c77a7a3
2 ändrade filer med 118 tillägg och 115 borttagningar
  1. 17 15
      src/main/frontend/worker/db_worker.cljs
  2. 101 100
      src/main/frontend/worker/shared_service.cljs

+ 17 - 15
src/main/frontend/worker/db_worker.cljs

@@ -850,7 +850,7 @@
   (when-let [prev-graph (first @*service)]
     (close-db! prev-graph))
   (when (and graph (not= graph (first @*service)))
-    (p/let [service (shared-service/create-service graph
+    (p/let [service (shared-service/<create-service graph
                                                    (bean/->js fns)
                                                    #(on-become-master graph))]
       (assert (p/promise? (get-in service [:status :ready])))
@@ -868,20 +868,22 @@
   []
   (let [proxy-object (->>
                       fns
-                      (map (fn [[k f]]
-                             [k (fn [& args]
-                                  (let [[_graph service] @*service
-                                        method-k (keyword (first args))]
-                                    (cond
-                                      (or @shared-service/*master-client?
-                                          (contains? #{:thread-api/init-shared-service :thread-api/sync-app-state} method-k)
-                                          (nil? service))
-                                      (apply f args)
-
-                                      :else
-                                      ;; ensure service is ready
-                                      (p/let [_ready-value (get-in service [:status :ready])]
-                                        (js-invoke (:proxy service) k args)))))]))
+                      (map
+                       (fn [[k f]]
+                         [k
+                          (fn [& args]
+                            (let [[_graph service] @*service
+                                  method-k (keyword (first args))]
+                              (cond
+                                (or (contains? #{:thread-api/init-shared-service :thread-api/sync-app-state} method-k)
+                                    (nil? service))
+                                ;; only proceed down this branch before shared-service is initialized
+                                (apply f args)
+
+                                :else
+                                ;; ensure service is ready
+                                (p/let [_ready-value (get-in service [:status :ready])]
+                                  (js-invoke (:proxy service) k args)))))]))
                       (into {})
                       bean/->js)]
     (glogi-console/install!)

+ 101 - 100
src/main/frontend/worker/shared_service.cljs

@@ -46,7 +46,12 @@
   []
   (str (random-uuid)))
 
-(defn- get-client-id
+(defn- do-not-wait
+  [promise]
+  promise
+  nil)
+
+(defn- <get-client-id
   []
   (let [id (random-id)]
     (p/let [client-id (js/navigator.locks.request id #js {:mode "exclusive"}
@@ -56,17 +61,18 @@
                                                            (some #(when (= (.-name %) id) %))
                                                            .-clientId))))]
       (assert (some? client-id))
-      (do ;; don't wait this promise
-        (js/navigator.locks.request client-id #js {:mode "exclusive"}
-                                    (fn [_] (p/deferred)))
-        nil)
+      (do-not-wait
+       (js/navigator.locks.request client-id #js {:mode "exclusive"}
+                                   ;; never release it
+                                   (fn [_] (p/deferred))))
       (log/debug :client-id client-id)
       client-id)))
 
-(defn- ensure-client-id
+(defn- <ensure-client-id
   []
   (or @*client-id
-      (reset! *client-id (get-client-id))))
+      (p/let [client-id (<get-client-id)]
+        (reset! *client-id client-id))))
 
 (defn- ensure-common-channel
   [service-name]
@@ -92,56 +98,55 @@
   (reset! *client-channel-listener listener-fn)
   (.addEventListener client-channel "message" listener-fn))
 
-(defn- apply-target-f!
+(defn- <apply-target-f!
   [target method args]
-  (when-let [f (gobj/get target method)]
+  (let [f (gobj/get target method)]
+    (assert (some? f) {:method method})
     (apply f args)))
 
-(defn- check-master-or-slave-client!
+(defn- <check-master-or-slave-client!
   "Check if the current client is the master (otherwise, it is a slave)"
-  [service-name on-become-master on-become-slave]
-  (p/let [client-id (ensure-client-id)]
-    (do
-      ;; don't wait this locks.request
-      (js/navigator.locks.request service-name #js {:mode "exclusive", :ifAvailable true}
-                                  (fn [lock]
-                                    (p/let [^js locks (js/navigator.locks.query)
-                                            locked? (some #(when (and (= (.-name %) service-name)
-                                                                      (= (.-clientId %) client-id))
-                                                             true)
-                                                          (.-held locks))]
-                                      (cond
-                                        (and locked? lock) ;become master
-                                        (p/do!
-                                         (reset! *master-client? true)
-                                         (on-become-master)
-                                         (reset! *master-client-lock (p/deferred))
-                                          ;; Keep lock until context destroyed
-                                         @*master-client-lock)
-
-                                        (and locked? (nil? lock)) ;already locked by this client, do nothing
-                                        (assert (true? @*master-client?))
-
-                                        (not locked?) ;become slave
-                                        (p/do!
-                                         (reset! *master-client? false)
-                                         (on-become-slave))))))
-      nil)))
+  [service-name <on-become-master <on-become-slave]
+  (p/let [client-id (<ensure-client-id)]
+    (do-not-wait
+     (js/navigator.locks.request
+      service-name #js {:mode "exclusive", :ifAvailable true}
+      (fn [lock]
+        (p/let [^js locks (js/navigator.locks.query)
+                locked? (some #(when (and (= (.-name %) service-name)
+                                          (= (.-clientId %) client-id))
+                                 true)
+                              (.-held locks))]
+          (cond
+            (and locked? lock) ;become master
+            (p/do!
+             (reset! *master-client? true)
+             (<on-become-master)
+             (reset! *master-client-lock (p/deferred))
+              ;; Keep lock until context destroyed
+             @*master-client-lock)
+
+            (and locked? (nil? lock)) ;already locked by this client, do nothing
+            (assert (true? @*master-client?))
+
+            (not locked?) ;become slave
+            (p/do!
+             (reset! *master-client? false)
+             (<on-become-slave)))))))))
 
 (defn- clear-old-service!
   []
-  (p/do!
-   (release-master-client-lock!)
-   (reset! *master-client? false)
-   (when-let [^js channel @*common-channel]
-     (.close channel))
-   (when-let [^js channel @*client-channel]
-     (.close channel))
-   (reset! *common-channel nil)
-   (reset! *client-channel nil)
-   (reset! *common-channel-listener nil)
-   (reset! *client-channel-listener nil)
-   (vreset! *requests-in-flight (sorted-map))))
+  (release-master-client-lock!)
+  (reset! *master-client? false)
+  (when-let [^js channel @*common-channel]
+    (.close channel))
+  (when-let [^js channel @*client-channel]
+    (.close channel))
+  (reset! *common-channel nil)
+  (reset! *client-channel nil)
+  (reset! *common-channel-listener nil)
+  (reset! *client-channel-listener nil)
+  (vreset! *requests-in-flight (sorted-map)))
 
 (defn- on-response-handler
   [event]
@@ -160,7 +165,7 @@
     (let [{:keys [type method args id]} (bean/->clj (.-data event))]
       (when (identical? "request" type)
         (p/let [[result error]
-                (-> (p/then (apply-target-f! target method args)
+                (-> (p/then (<apply-target-f! target method args)
                             (fn [res] [res nil]))
                     (p/catch
                      (fn [e] [nil (if (instance? js/Error e)
@@ -173,7 +178,7 @@
                                          :error error
                                          :method-key (first args)})))))))
 
-(defn- slave-registered-handler
+(defn- <slave-registered-handler
   [service-name slave-client-id event *register-finish-promise?]
   (let [slave-client-id* (:slave-client-id event)]
     (when (= slave-client-id slave-client-id*)
@@ -184,12 +189,13 @@
                             (= slave-client-id (.-clientId l))))
                (.-pending locks))]
         (when-not already-watching?     ;dont watch multiple times
-          (js/navigator.locks.request service-name #js {:mode "exclusive"}
-                                      (fn [_lock]
-                                        ;; The master has gone, elect the new master
-                                        (log/debug "master has gone" nil)
-                                        (reset! *master-re-check-trigger :re-check)))))
-      (p/resolve! @*register-finish-promise?))))
+          (do-not-wait
+           (js/navigator.locks.request service-name #js {:mode "exclusive"}
+                                       (fn [_lock]
+                                         ;; The master has gone, elect the new master
+                                         (log/debug "master has gone" nil)
+                                         (reset! *master-re-check-trigger :re-check)))))
+        (p/resolve! @*register-finish-promise?)))))
 
 (defn- <re-requests-in-flight-on-slave!
   [client-channel]
@@ -213,7 +219,7 @@
      (p/run!
       (fn [[id {:keys [method args resolve-fn reject-fn]}]]
         (->
-         (p/let [result (apply-target-f! target method args)]
+         (p/let [result (<apply-target-f! target method args)]
            (resolve-fn result))
          (p/catch (fn [e]
                     (log/error "Error processing request" e)
@@ -221,7 +227,7 @@
          (p/finally (fn []
                       (vswap! *requests-in-flight dissoc id)))))))))
 
-(defn- on-become-slave
+(defn- <on-become-slave
   [slave-client-id service-name common-channel status-ready-promise]
   (let [client-channel (ensure-client-channel slave-client-id service-name)
         *register-finish-promise? (atom nil)
@@ -241,7 +247,7 @@
             (<register)
             (<re-requests-in-flight-on-slave! client-channel))
            "slave-registered"
-           (slave-registered-handler service-name slave-client-id event* *register-finish-promise?)
+           (<slave-registered-handler service-name slave-client-id event* *register-finish-promise?)
 
            "sync-db-changes"
            (.postMessage js/self data)
@@ -256,7 +262,7 @@
                 (log/error :on-become-slave e)
                 (p/rejected e))))))
 
-(defn- on-become-master
+(defn- <on-become-master
   [master-client-id service-name common-channel target on-become-master-handler status-ready-deferred-p]
   (log/debug :become-master master-client-id :service service-name)
   (listen-common-channel
@@ -265,10 +271,11 @@
      (let [{:keys [slave-client-id type]} (bean/->clj (.-data event))]
        (when (= type "slave-register")
          (let [client-channel (js/BroadcastChannel. (get-broadcast-channel-name slave-client-id service-name))]
-           (js/navigator.locks.request slave-client-id #js {:mode "exclusive"}
-                                       (fn [_]
-                                         (log/debug :slave-has-gone slave-client-id)
-                                         (.close client-channel)))
+           (do-not-wait
+            (js/navigator.locks.request slave-client-id #js {:mode "exclusive"}
+                                        (fn [_]
+                                          (log/debug :slave-has-gone slave-client-id)
+                                          (.close client-channel))))
            (listen-client-channel client-channel (create-on-request-handler client-channel target))
            (.postMessage common-channel (bean/->js {:type "slave-registered"
                                                     :slave-client-id slave-client-id
@@ -282,55 +289,49 @@
    (<re-requests-in-flight-on-master! target)
    (p/resolve! status-ready-deferred-p)))
 
-(defn create-service
+(defn <create-service
   [service-name target on-become-master-handler]
-  (p/let [_ (clear-old-service!)
-          status {:ready (p/deferred)}
+  (clear-old-service!)
+  (p/let [status {:ready (p/deferred)}
           common-channel (ensure-common-channel service-name)
-          client-id (ensure-client-id)
-          check-master-slave-fn!
+          client-id (<ensure-client-id)
+          <check-master-slave-fn!
           (fn []
-            (check-master-or-slave-client!
+            (<check-master-or-slave-client!
              service-name
-             #(on-become-master
+             #(<on-become-master
                client-id service-name common-channel target
                on-become-master-handler (:ready status))
-             #(on-become-slave
+             #(<on-become-slave
                client-id service-name common-channel (:ready status))))]
-    (check-master-slave-fn!)
+    (<check-master-slave-fn!)
 
     (add-watch *master-re-check-trigger :check-master
                (fn [_ _ _ new-value]
                  (when (= new-value :re-check)
                    (p/do!
-                    (p/delay 100)     ; why need delay here?
-                    (check-master-slave-fn!)))))
+                    (p/delay 100)      ; why need delay here?
+                    (<check-master-slave-fn!)))))
 
     {:proxy (js/Proxy. target
                        #js {:get (fn [target method]
-                                   (cond
-                                     (#{:then :catch :finally} (keyword method))
-                                     ;; Return nil for these methods to allow promise chaining to work correctly
-                                     nil
-
-                                     :else
-                                     (fn [args]
-                                       (let [master-client? @*master-client?]
-                                         (if master-client?
-                                           (apply-target-f! target method args)
-                                           (p/create
-                                            (fn [resolve-fn reject-fn]
-                                              (let [request-id (next-request-id)
-                                                    client-channel (ensure-client-channel client-id service-name)]
-                                                (vswap! *requests-in-flight assoc request-id {:method method
-                                                                                              :args args
-                                                                                              :resolve-fn resolve-fn
-                                                                                              :reject-fn reject-fn})
-                                                (.postMessage client-channel (bean/->js
-                                                                              {:id request-id
-                                                                               :type "request"
-                                                                               :method method
-                                                                               :args args}))))))))))})
+                                   (assert (identical? "remoteInvoke" method) method)
+                                   (fn [args]
+                                     (if @*master-client?
+                                       (<apply-target-f! target method args)
+                                       (let [request-id (next-request-id)
+                                             client-channel (ensure-client-channel client-id service-name)]
+                                         (p/create
+                                          (fn [resolve-fn reject-fn]
+                                            (vswap! *requests-in-flight assoc request-id {:method method
+                                                                                          :args args
+                                                                                          :resolve-fn resolve-fn
+                                                                                          :reject-fn reject-fn})
+                                            (.postMessage client-channel (bean/->js
+                                                                          {:id request-id
+                                                                           :type "request"
+                                                                           :method method
+                                                                           :args args}))))))))})
      :status status}))
 
 (defn broadcast-to-clients!