Search code examples
tcl

Can multiple instances of the same Tcl program share the same local server started by the first opened instance?


I apologize for asking another odd question about Tcl. I've been struggling a bit with figuring out how to handle some items if a user were to open more than one instance of a Tcl local desktop program. The program sets up a local server and connects to a small number of local SQLite databases (some of which attach in-memory databases which, evidently, cannot be shared across the Tcl program instances because they are separate processes) and uses a browser as the UI.

Would it not be possible to run only one local server regardless of the number of instances the user might open? For example, if another database file or a regular file is used such that, when the program starts, it looks for the file and, if it does not exist, it creates it and writes the Tcl-selected port number after starting the server. If the file does exist, it reads the port number from the file and attempts to connect as a client and pass a request. If it gets the expected response, indicating that a server is already running, then it closes the file and client connection, and continues with its start up without starting another server.

It appears that in this set-up, all the Tcl program instances share the same database connections including access to the same in-memory databases, since all are owned by the single shared server.

My question is, Is this a viable option and, if so, is there a better method of testing if an earlier instance has already started a server? (Of course, I'm going to see if it will work; but my question is, even if it does appear to work, is it a bad approach for some reason?)

Thank you for considering my question.


I don't know if this would ever be useful to anyone else (and I preface it with the fact that I'm not a professional and wouldn't recommend anything I coded) but, after messing around a bit, I realized that my question wasn't really how to have multiple Tcl instances share a server but, rather, to force additional attempts at opening another instance to open another browser instance and direct it to the already open Tcl-server (if that makes any sense).

This example has been working; and when it determines that the server is already running, it opens the browser in the background and passes it off to the running server and closes. I don't know if it's a good or bad approach but, having one server with one connection to each database, results in the multiple browser instances "knowing" what the user may have typed but not yet saved to disk in a different instance because tracking it in an in-memory database in a single process.

if { [catch {
       set fdPort [open server_data.txt {RDWR CREAT}]
       chan seek $fdPort 0
       set data [chan get $fdPort] } result catchDict] } {
  chan puts stdout {Failed to read/create server_data.txt.}
  chan puts stdout "Error: $result $catchDict"
  chan close $fdPort
  exit
}
# If the file was just created, it will be empty;
# and if length != 2, it is bad/incomplete data.
if { [llength $data] == 2 } {
  set addr [lindex $data 0]
  set port [lindex $data 1]
  if { [catch { set sock [socket $addr $port] } result catchDict] } {
    # The address:port does not exist or refused
    # to accept a connection, which must indicate
    # it is not running the Tcl server.
    chan puts stdout {Failed to connect to a server using stored data.\
       Will start one.}
    chan puts stdout "Error: $result $catchDict"
  } else {
    # Now that the socket has been opened, pass
    # the fake serverping and see if get a pong.
    chan configure $sock -buffering full -encoding iso8859-1 -translation crlf
    chan puts $sock "GET /serverping HTTP/1.1"
    chan puts $sock ""
    chan flush $sock
    chan close $sock write
    # WARNING Need to open the socket as -async
    # or time out after a very short interval
    # to avoid blocking.
    if { [chan gets $sock] eq {serverpong} } {
      # Open the browser directed to the url and port
      # instructing it to requesst the log-in page from
      # the already running server; and, then, exit this
      # Tcl script. NOTE Could exit and not permit more
      # than one connection. Or, could track connections
      # in an in-memory database by user name and limit
      # those connections to one per user name.

      # set pids [exec firefox -P "ToolTest" -no-remote -new-instance -offline \
      #     -private -url http://${addr}:${port}/login &]
      # puts "-------------------------------/n$pids"
      exec /usr/lib/brave-browser/brave --args --new-window\
          --window-size=3000,1800 --app=http://${addr}:${port}/login &
      chan close $sock
      chan close $fdPort
      exit
    }
    # Otherwise, no pong from the server and, therefore,
    # must not be the Tcl server on this port.
    chan puts stdout {Server did not respond; will start one.}
  }
}

# Source main.tcl which will return the data
# to write to server_data.txt.
lassign [source main.tcl] addr port
chan seek $fdPort 0
chan puts $fdPort "$addr $port"
chan close $fdPort

Solution

  • If the program also uses Tk, then you can do it easily on Linux. Use tk appname to set a unique name for your application (you can usually use something like the base name of the main script or the directory containing it; the right choice tends to be obvious). Then use winfo interps to list the Tk-enabled interpreters with that base name to find the one which was defined first (it typically won't have a number suffix). Finally, if you aren't the lead app then just use send to ask the lead to do things for you.

    package require Tk
    set name myScript
    tk appname $name
    if {$name ne [tk appname] && $name in [winfo interps]} {
        send $name [list openFiles {*}$argv]
        exit
    }