Make Resources factory_func() and destroy_func() asynchronous, add some locking
authorNick Downing <nick@ndcode.org>
Tue, 4 Dec 2018 07:42:56 +0000 (18:42 +1100)
committerNick Downing <nick@ndcode.org>
Tue, 4 Dec 2018 07:42:56 +0000 (18:42 +1100)
Resources.js
Server.js
Site.js
cli.js

index 2752035..fb29e17 100644 (file)
@@ -30,28 +30,47 @@ let Resources = function(diag) {
   this.diag = diag || false
 }
 
-Resources.prototype.ref = function(key, factory_func, destroy_func) {
+Resources.prototype.ref = async function(key, factory_func, destroy_func) {
   result = this.map.get(key)
+  let value
   if (result === undefined) {
-    result = {refs: 0, value: factory_func(), destroy_func: destroy_func}
+    result = {
+      refs: 0,
+      value: factory_func(), // don't await it here
+      destroy_func: destroy_func
+    }
     this.map.set(key, result)
+    try {
+      value = await result.value
+    }
+    catch (err) {
+      this.map.delete(key)
+      throw err
+    }
   }
+  else
+    value = await result.value
   result.refs += 1
   if (this.diag)
     console.log(`ref ${key} refs -> ${result.refs}`)
-  return result.value
+  return value
 }
 
-Resources.prototype.unref = function(key) {
+Resources.prototype.unref = async function(key) {
   result = this.map.get(key)
   assert(result !== undefined && result.refs > 0)
   result.refs -= 1
   if (this.diag)
     console.log(`unref ${key} refs -> ${result.refs}`)
   if (result.refs === 0) {
-    if (result.destroy_func !== undefined)
-      result.destroy_func(result.value)
     this.map.delete(key)
+    if (result.destroy_func !== undefined)
+      try {
+        await result.destroy_func(await result.value)
+      }
+      catch (err) {
+        console.err(err.stack || err.message)
+      }
   }
 }
 
index 1e09429..594eb6c 100644 (file)
--- a/Server.js
+++ b/Server.js
@@ -71,11 +71,11 @@ let Server = function(resources, options, prev_server) {
     /*await*/ this.respond(request, response, listener.options.protocol)
 }
 
-Server.prototype.start = function() {
+Server.prototype.start = async function() {
   assert(this.jst_cache === undefined)
-  this.jst_cache = this.resources.ref(
+  this.jst_cache = await this.resources.ref(
     'jst_cache:.',
-    () => new JSTCache('.', {_jst_server: jst_server}, true)
+    async () => new JSTCache('.', {_jst_server: jst_server}, true)
   )
 
   assert(this.listeners === undefined)
@@ -108,23 +108,23 @@ Server.prototype.start = function() {
     default:
       assert(false)
     }
-    let listener = this.resources.ref(
+    let listener = await this.resources.ref(
       `listener:${JSON.stringify(options)}`,
-      () => new Listener(/*this.request_func*/undefined, options, true),
-      listener => listener.stop() // ignore returned Promise
+      async () => new Listener(undefined, options, true),
+      listener => /*await*/ listener.stop()
     )
     listener.request_func = this.request_func
     this.listeners.push(listener)
   }
 }
 
-Server.prototype.stop = function() {
+Server.prototype.stop = async function() {
   assert(this.jst_cache !== undefined)
-  this.resources.unref('jst_cache:.')
+  await this.resources.unref('jst_cache:.')
 
   assert(this.listeners !== undefined)
   for (let i = 0; i < this.listeners.length; ++i)
-    this.resources.unref(
+    await this.resources.unref(
       `listener:${JSON.stringify(this.listeners[i].options)}`
     )
 }
@@ -226,9 +226,10 @@ Server.prototype.respond = async function(request, response, protocol) {
         root = this.roots[host.root]
       else {
         root = {
-          jst_cache: this.resources.ref(
+          jst_cache: await this.resources.ref(
             `jst_cache:${host.root}`,
-            () => new JSTCache(host.root, {_jst_server: jst_server}, true)
+            async () =>
+              new JSTCache(host.root, {_jst_server: jst_server}, true)
           ),
           site: undefined
         }
@@ -238,8 +239,15 @@ Server.prototype.respond = async function(request, response, protocol) {
         `${host.root}/_config/site.jst`,
         true
       )
-      if (config !== undefined)
-        root.site = await config(this.resources, host.root, root.site)
+      if (config !== undefined) {
+        // we should somehow block further config changes during this:
+        let prev_site = root.site
+        let site = await config(this.resources, host.root, prev_site)
+        await site.start()
+        root.site = site
+        if (prev_site !== undefined)
+          await prev_site.stop()
+      }
       return root.site.respond(
         {
           parsed_url: parsed_url,
diff --git a/Site.js b/Site.js
index 36fb5d9..66a25c6 100644 (file)
--- a/Site.js
+++ b/Site.js
@@ -65,50 +65,113 @@ let Site = function(resources, root, options) {
     options || {}
   )
 
+  this.json_cache = undefined
+  this.json_cache_rw = undefined
+  this.jst_cache = undefined
+  this.less_css_cache = undefined
+  this.min_css_cache = undefined
+  this.min_js_cache = undefined
+  this.min_html_cache = undefined
+  this.min_svg_cache = undefined
+  this.text_cache = undefined
+  this.zip_cache = undefined
+
   this.socket_io_connect_listeners = [] // later will use this for destruction
-  this.json_cache = resources.ref(
+}
+
+Site.prototype.start = async function() {
+  assert(this.json_cache === undefined)
+  this.json_cache = await this.resources.ref(
     'json_cache',
-    () => new JSONCache(true)
+    async () => new JSONCache(true)
   )
-  this.json_cache_rw = resources.ref(
+
+  assert(this.json_cache_rw === undefined)
+  this.json_cache_rw = await this.resources.ref(
     'json_cache_rw',
-    () => new JSONCacheRW(true)
+    async () => new JSONCacheRW(true)
   )
-  this.jst_cache = resources.ref(
-    `jst_cache:${root}`,
-    () => new JSTCache(root, {_jst_server: jst_server}, true)
+
+  assert(this.jst_cache === undefined)
+  this.jst_cache = await this.resources.ref(
+    `jst_cache:${this.root}`,
+    async () => new JSTCache(this.root, {_jst_server: jst_server}, true)
   )
-  this.less_css_cache = resources.ref(
-    `less_css_cache:${root}`,
-    () => new LessCSSCache(root, true)
+
+  assert(this.less_css_cache === undefined)
+  this.less_css_cache = await this.resources.ref(
+    `less_css_cache:${this.root}`,
+    async () => new LessCSSCache(this.root, true)
   )
-  this.min_css_cache = resources.ref(
+
+  assert(this.min_css_cache === undefined)
+  this.min_css_cache = await this.resources.ref(
     'min_css_cache',
-    () => new MinCSSCache(true)
+    async () => new MinCSSCache(true)
   )
-  this.min_js_cache = resources.ref(
+
+  assert(this.min_js_cache === undefined)
+  this.min_js_cache = await this.resources.ref(
     'min_js_cache',
-    () => new MinJSCache(true)
+    async () => new MinJSCache(true)
   )
-  this.min_html_cache = resources.ref(
+
+  assert(this.min_html_cache === undefined)
+  this.min_html_cache = await this.resources.ref(
     'min_html_cache',
-    () => new MinHTMLCache(true)
+    async () => new MinHTMLCache(true)
   )
-  this.min_svg_cache = resources.ref(
+
+  assert(this.min_svg_cache === undefined)
+  this.min_svg_cache = await this.resources.ref(
     'min_svg_cache',
-    () => new MinSVGCache(true)
+    async () => new MinSVGCache(true)
   )
-  this.text_cache = resources.ref(
+
+  assert(this.text_cache === undefined)
+  this.text_cache = await this.resources.ref(
     'text_cache',
-    () => new TextCache(true)
+    async () => new TextCache(true)
   )
-  this.zip_cache = resources.ref(
+
+  assert(this.zip_cache === undefined)
+  this.zip_cache = await this.resources.ref(
     'zip_cache',
-    () => new ZipCache(true)
+    async () => new ZipCache(true)
   )
 }
 
-Site.prototype = Object.create(Site.prototype)
+Site.prototype.stop = async function() {
+  assert(this.json_cache !== undefined)
+  await this.resources.unref('json_cache')
+
+  assert(this.json_cache_rw !== undefined)
+  await this.resources.unref('json_cache_rw')
+
+  assert(this.jst_cache !== undefined)
+  await this.resources.unref(`jst_cache:${this.root}`)
+
+  assert(this.less_css_cache !== undefined)
+  await this.resources.unref(`less_css_cache:${this.root}`)
+
+  assert(this.min_css_cache !== undefined)
+  await this.resources.unref('min_css_cache')
+
+  assert(this.min_js_cache !== undefined)
+  await this.resources.unref('min_js_cache')
+
+  assert(this.min_html_cache !== undefined)
+  await this.resources.unref('min_html_cache')
+
+  assert(this.min_svg_cache !== undefined)
+  await this.resources.unref('min_svg_cache')
+
+  assert(this.text_cache !== undefined)
+  await this.resources.unref('text_cache')
+
+  assert(this.zip_cache !== undefined)
+  await this.resources.unref('zip_cache')
+}
 
 Site.prototype.serve_internal = function(response, status, mime_type, data) {
   response.statusCode = status
diff --git a/cli.js b/cli.js
index f8979da..baed54a 100755 (executable)
--- a/cli.js
+++ b/cli.js
@@ -30,27 +30,31 @@ let JSTCache = require('@ndcode/jst_cache')
 let Resources = require('./Resources')
 let Server = require('./Server')
 
-let resources = new Resources()
-let jst_cache = resources.ref(
-  'jst_cache:.',
-  () => new JSTCache('.', {_jst_server: jst_server}, true)
-)
-let server = undefined
+;(
+  async () => {
+    let resources = new Resources()
+    let jst_cache = await resources.ref(
+      'jst_cache:.',
+      async () => new JSTCache('.', {_jst_server: jst_server}, true)
+    )
+    let server = undefined
 
-// refresh the config immediately, then every 5 seconds,
-// use setTimeout() instead of setInterval() to avoid bunches
-// of calls after the computer has been suspended for a while
-let refresh_config = async () => {
-  let config = await jst_cache.get('_config/server.jst', true)
-  if (config !== undefined) {
-    let prev_server = server
-    server = await config(resources, prev_server)
-    await server.start()
-    if (prev_server !== undefined)
-      await prev_server.stop()
+    // refresh the config immediately, then every 5 seconds,
+    // use setTimeout() instead of setInterval() to avoid bunches
+    // of calls after the computer has been suspended for a while
+    let refresh_config = async () => {
+      let config = await jst_cache.get('_config/server.jst', true)
+      if (config !== undefined) {
+        let prev_server = server
+        server = await config(resources, prev_server)
+        await server.start()
+        if (prev_server !== undefined)
+          await prev_server.stop()
+      }
+      server.kick()
+      setTimeout(refresh_config, 5000)
+      // returned Promise will be ignored
+    }
+    refresh_config()
   }
-  server.kick()
-  setTimeout(refresh_config, 5000)
-  // returned Promise will be ignored
-}
-refresh_config()
+)()