Repository: PumasAI/QuartoNotebookRunner.jl (julia)

Issue:
Add a server status check endpoint
It would make sense if quarto first asked a server that is supposedly already running for a status before proceeding. This status should probably include a list of all worker processes and the quarto documents they are attached to. For one-shot renders, we want to start with a fresh process, so we'd also need this information to decide whether to close the current worker. Currently, we send a close command from quarto, knowing that it will throw an error if there's no worker, which is not the cleanest solution.

Pull request description:
`isopen` can be used to check that a file already has a worker opened for it, which can then be `close`d if desired. The `isready` command is just to have something for quarto to establish that the server is functioning at all, before sending further commands to it.

Fixes https://github.com/PumasAI/QuartoNotebookRunner.jl/issues/39

Patch:
diff --git a/src/socket.jl b/src/socket.jl
index 8ed59abc..cb0a0758 100644
--- a/src/socket.jl
+++ b/src/socket.jl
@@ -13,7 +13,7 @@ Message schema:
 
 ```json
 {
-    type: "run" | "close" | "stop"
+    type: "run" | "close" | "stop" | "isopen" | "isready"
     content: string | { file: string, options: string | { ... } }
 }
 ```
@@ -33,6 +33,14 @@ A description of the message types:
 
  -  `stop` - Stop the server. The server will return a response with a `message`
     field set to `Server stopped.`.
+
+ -  `isopen` - Check if a notebook specified by the absolute path in `content`
+    has already been `run` and therefore has a worker open in the background.
+    If so return `true` else `false`. If `true` then you should be able to call
+    `close` for that file without an error.
+
+ -  `isready` - Returns `true` if the server is ready to accept commands. Should
+    never return `false`.
 """
 function serve(; port = get(ARGS, 1, rand(1024:65535)), showprogress::Bool = true)
     getport(port::Integer) = port
@@ -76,6 +84,8 @@ function serve(; port = get(ARGS, 1, rand(1024:65535)), showprogress::Bool = tru
                             _write_json(socket, (; message = "Server stopped."))
                             close(socket)
                             close(socket_server)
+                        elseif json.type == "isready"
+                            _write_json(socket, true)
                         else
                             _write_json(
                                 socket,
@@ -98,9 +108,10 @@ function _handle_response(
 )
     @debug "debugging" request notebooks = collect(keys(notebooks.workers))
     type = request.type
-    file = _get_file(request.content)
 
-    type in ("close", "run") || return _log_error("Unknown request type: $type")
+    type in ("close", "run", "isopen") || return _log_error("Unknown request type: $type")
+
+    file = _get_file(request.content)
 
     # Closing:
 
@@ -132,6 +143,10 @@ function _handle_response(
         end
     end
 
+    if type == "isopen"
+        return haskey(notebooks.workers, file)
+    end
+
     # Shouldn't get to this point.
     error("unreachable reached.")
 end


Test patch:
diff --git a/test/testsets/socket_server/client.js b/test/testsets/socket_server/client.js
index f637a47b..061c0968 100644
--- a/test/testsets/socket_server/client.js
+++ b/test/testsets/socket_server/client.js
@@ -9,6 +9,8 @@ function handle() {
     const run = (file) => toJSON({ type: 'run', content: file });
     const close = (file) => toJSON({ type: 'close', content: file || '' });
     const stop = () => toJSON({ type: 'stop', content: '' });
+    const isopen = (file) => toJSON({ type: 'isopen', content: file });
+    const isready = () => toJSON({ type: 'isready', content: '' });
 
     const notebook = (arg) => {
         if (arg) {
@@ -27,6 +29,10 @@ function handle() {
             return close(notebook(arg));
         case 'stop':
             return stop();
+        case 'isopen':
+            return isopen(notebook(arg));
+        case 'isready':
+            return isready();
         default:
             throw new Error('Invalid command.');
     }
diff --git a/test/testsets/socket_server/socket_server.jl b/test/testsets/socket_server/socket_server.jl
index d5ce5bb8..95685fe8 100644
--- a/test/testsets/socket_server/socket_server.jl
+++ b/test/testsets/socket_server/socket_server.jl
@@ -11,20 +11,31 @@ include("../../utilities/prelude.jl")
 
         cell_types = "../../examples/cell_types.qmd"
 
-        d1 = json(`$node $client $port run $(cell_types)`)
-        @test length(d1["notebook"]["cells"]) == 6
+        @test json(`$node $client $port isready`)
+
+        d1 = json(`$node $client $port isopen $(cell_types)`)
+        @test d1 == false
 
         d2 = json(`$node $client $port run $(cell_types)`)
-        @test d1 == d2
+        @test length(d2["notebook"]["cells"]) == 6
 
-        d3 = json(`$node $client $port close $(cell_types)`)
-        @test d3["status"] == true
+        d3 = json(`$node $client $port isopen $(cell_types)`)
+        @test d3 == true
 
         d4 = json(`$node $client $port run $(cell_types)`)
-        @test d1 == d4
+        @test d2 == d4
+
+        d5 = json(`$node $client $port close $(cell_types)`)
+        @test d5["status"] == true
+
+        d6 = json(`$node $client $port isopen $(cell_types)`)
+        @test d6 == false
+
+        d7 = json(`$node $client $port run $(cell_types)`)
+        @test d2 == d7
 
-        d5 = json(`$node $client $port stop`)
-        @test d5["message"] == "Server stopped."
+        d8 = json(`$node $client $port stop`)
+        @test d8["message"] == "Server stopped."
 
         wait(server)
     end