;; Copyright 2006-2017, Matthew Welland.
;;
;; This file is part of Megatest.
;;
;; Megatest is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Megatest is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Megatest. If not, see <http://www.gnu.org/licenses/>.
;;
;;======================================================================
;;
;; This is the Megatest specific stuff for starting and maintaining a
;; server. Anything that talks to the server should go in client.scm (maybe - might get rid of client.scm)
;; General nanomsg stuff (not Megatest specific) should go in the
;; nmsg-transport.scm file.
;;
;;======================================================================
(require-extension (srfi 18) extras tcp s11n)
(use srfi-1 posix regex regex-case srfi-69 hostinfo md5 message-digest
directory-utils posix-extras matchable typed-records
pkts)
(use spiffy uri-common intarweb http-client spiffy-request-vars)
(declare (unit server))
(declare (uses common))
(declare (uses db))
(import db)
;; Basic stuff for safely kicking off a server
(declare (uses portlogger))
(import portlogger)
(declare (uses nmsg-transport))
(import nmsg-transport)
;; Might want to bring the daemonizing back
;; (declare (uses daemon))
(include "common_records.scm")
(include "db_records.scm")
;;======================================================================
;; P K T S S T U F F
;;======================================================================
;;======================================================================
;; N A N O M S G B A S E D S E R V E R
;;======================================================================
(defstruct area
(conn #f)
(port #f)
(myaddr #f)
(hosts (make-hash-table))
pktid ;; get pkt from hosts table if needed
pktspec
pktfile
pktsdir
mtrah
(mutex (make-mutex))
)
;; make it a global? Well, it is local to area module
(define *area-conndat* (make-area))
(area-pktspec-set! *area-conndat*
`((server (hostname . h)
(port . p)
(pid . i)
)))
(define (server:get-mtrah)
(or (get-environment-variable "MT_RUN_AREA_HOME")
(if (file-exists? "megatest.config")
(current-directory)
#f)))
;; get a port
;; start the nmsg server
;; look for other servers
;; contact other servers and compile list of servers
;; there are two types of server
;; main servers - dashboards, runners and dedicated servers - need pkt
;; passive servers - test executers, step calls, list-runs - no pkt
;;
(define (server:start-nmsg #!optional (force-server-type #f))
(mutex-lock! (area-mutex *area-conndat*))
(let* ((server-type (or force-server-type
(if (args:any? "-run" "-server")
'main
'passive)))
(port-num (portlogger:open-run-close portlogger:find-port))
(area-conn (nmsg:start-server port-num))
(pktspec (area-pktspec *area-conndat*))
(mtdir (or (server:get-mtrah)
(begin
(print "ERROR: megatest.config not found and MT_RUN_AREA_HOME is not set.")
#f)))
(pktdir (conc mtdir
"/.server-pkts")))
(if (not mtdir)
#f
(begin
(if (not (directory? pktdir))(create-directory pktdir))
;; server is started, now create pkt if needed
(if (eq? server-type 'main)
(begin
(area-pktid-set! *area-conndat*
(write-alist->pkt
pktdir
`((hostname . ,(get-host-name))
(port . ,port-num)
(pid . ,(current-process-id)))
pktspec: pktspec
ptype: 'server))
(area-pktfile-set! *area-conndat* (conc pktdir "/" (area-pktid *area-conndat*) ".pkt"))))
;; set all the area info in the
(area-pktsdir-set! *area-conndat* pktdir)
(area-mtrah-set! *area-conndat* mtdir)
(area-conn-set! *area-conndat* area-conn)
(area-port-set! *area-conndat* port-num)
(mutex-unlock! (area-mutex *area-conndat*))
#t))))
;; Call this to start the actual server
;;
;; start_server
;;
;; mode: '
;;
(define (server:launch mode)
(let ((start-time (current-seconds)))
(server:start-nmsg mode)
(let loop ((dead-time (- (current-seconds) start-time)))
(thread-sleep! 1)
(if (< dead-time 10)
(loop (- (current-seconds) start-time))
(print "Timed out. Exiting")))))
(define (server:shutdown)
(let ((conn (area-conn *area-conndat*))
(pktf (area-pktfile *area-conndat*))
(port (area-port *area-conndat*)))
(if conn
(begin
(if pktf (delete-file* pktf))
(server:send-all "imshuttingdown")
(nmsg:close conn)
(portlogger:open-run-close portlogger:release-port port)))))
(define (server:send-all msg)
#f)
;; given a area record look up all the packets
(define (server:get-all-server-pkts rec)
(let ((all-pkt-files (glob (conc (area-pktsdir rec) "/*.pkt")))
(pktspec (area-pktspec rec)))
(map (lambda (pkt-file)
(read-pkt->alist pkt-file pktspec: pktspec))
all-pkt-files)))
;; look up all pkts and get the server id (the hash), port, host/ip
;; store this info in the global struct *area-conndat*
;;
(define (server:get-all)
;; readll all pkts
;; foreach pkt; if it isn't me ping the server; if alive, add to hosts hash, else rm the pkt
'())
;; send out an "I'm about to exit notice to all known servers"
;;
(define (server:announce-death)
'())
(define (server:get-my-best-address)
(ip->string (car (filter (lambda (x)
(not (eq? (u8vector-ref x 0) 127)))
(vector->list (hostinfo-addresses (hostname->hostinfo "zeus")))))))
;; whoami? I am my pkt
;;
(define (server:whoami? area)
(hash-table-ref/default (area-hosts area)(area-pktid area) #f))
;;======================================================================
;; S E R V E R U T I L I T I E S
;;======================================================================
;; get a signature for identifing this process
(define (server:get-process-signature)
(cons (get-host-name)(current-process-id)))
;; ;; Get the transport
;; (define (server:get-transport)
;; (if *transport-type*
;; *transport-type*
;; (let ((ttype (string->symbol
;; (or (args:get-arg "-transport")
;; (configf:lookup *configdat* "server" "transport")
;; "rpc"))))
;; (set! *transport-type* ttype)
;; ttype)))
;;
;; ;; Generate a unique signature for this server
;; (define (server:mk-signature)
;; (message-digest-string (md5-primitive)
;; (with-output-to-string
;; (lambda ()
;; (write (list (current-directory)
;; (argv)))))))
;;
;; ;; When using zmq this would send the message back (two step process)
;; ;; with spiffy or rpc this simply returns the return data to be returned
;; ;;
;; (define (server:reply return-addr query-sig success/fail result)
;; (debug:print-info 11 *default-log-port* "server:reply return-addr=" return-addr ", result=" result)
;; ;; (send-message pubsock target send-more: #t)
;; ;; (send-message pubsock
;; (case (server:get-transport)
;; ((rpc) (db:obj->string (vector success/fail query-sig result)))
;; ((http) (db:obj->string (vector success/fail query-sig result)))
;; ((fs) result)
;; (else
;; (debug:print-error 0 *default-log-port* "unrecognised transport type: " *transport-type*)
;; result)))
;;
;; ;; Given a run id start a server process ### NOTE ### > file 2>&1
;; ;; if the run-id is zero and the target-host is set
;; ;; try running on that host
;; ;; incidental: rotate logs in logs/ dir.
;; ;;
;; (define (server:run areapath) ;; areapath is *toppath* for a given testsuite area
;; (let* ((curr-host (get-host-name))
;; ;; (attempt-in-progress (server:start-attempted? areapath))
;; ;; (dot-server-url (server:check-if-running areapath))
;; (curr-ip (server:get-best-guess-address curr-host))
;; (curr-pid (current-process-id))
;; (homehost (common:get-homehost)) ;; configf:lookup *configdat* "server" "homehost" ))
;; (target-host (car homehost))
;; (testsuite (common:get-testsuite-name))
;; (logfile (conc areapath "/logs/server.log")) ;; -" curr-pid "-" target-host ".log"))
;; (cmdln (conc (common:get-megatest-exe)
;; " -server " (or target-host "-") (if (equal? (configf:lookup *configdat* "server" "daemonize") "yes")
;; " -daemonize "
;; "")
;; ;; " -log " logfile
;; " -m testsuite:" testsuite)) ;; (conc " >> " logfile " 2>&1 &")))))
;; (log-rotate (make-thread common:rotate-logs "server run, rotate logs thread"))
;; (load-limit (configf:lookup-number *configdat* "jobtools" "max-server-start-load" default: 3.0)))
;; ;; we want the remote server to start in *toppath* so push there
;; (push-directory areapath)
;; (debug:print 0 *default-log-port* "INFO: Trying to start server (" cmdln ") ...")
;; (thread-start! log-rotate)
;;
;; ;; host.domain.tld match host?
;; (if (and target-host
;; ;; look at target host, is it host.domain.tld or ip address and does it
;; ;; match current ip or hostname
;; (not (string-match (conc "("curr-host "|" curr-host"\\..*)") target-host))
;; (not (equal? curr-ip target-host)))
;; (begin
;; (debug:print-info 0 *default-log-port* "Starting server on " target-host ", logfile is " logfile)
;; (setenv "TARGETHOST" target-host)))
;;
;; (setenv "TARGETHOST_LOGF" logfile)
;; (thread-sleep! (/ (random 5000) 1000)) ;; add about a random (up to 5 seconds) initial delay. It seems pretty common that many running tests request a server at the same time
;; (common:wait-for-normalized-load load-limit " delaying server start due to load" target-host) ;; do not try starting servers on an already overloaded machine, just wait forever
;; (system (conc "nbfake " cmdln))
;; (unsetenv "TARGETHOST_LOGF")
;; (if (get-environment-variable "TARGETHOST")(unsetenv "TARGETHOST"))
;; (thread-join! log-rotate)
;; (pop-directory)))
;;
;; ;; given a path to a server log return: host port startseconds
;; ;;
;; (define (server:logf-get-start-info logf)
;; (let ((rx (regexp "^SERVER STARTED: (\\S+):(\\d+) AT ([\\d\\.]+)"))) ;; SERVER STARTED: host:port AT timesecs
;; (handle-exceptions
;; exn
;; (list #f #f #f) ;; no idea what went wrong, call it a bad server
;; (with-input-from-file
;; logf
;; (lambda ()
;; (let loop ((inl (read-line))
;; (lnum 0))
;; (if (not (eof-object? inl))
;; (let ((mlst (string-match rx inl)))
;; (if (not mlst)
;; (if (< lnum 500) ;; give up if more than 500 lines of server log read
;; (loop (read-line)(+ lnum 1))
;; (list #f #f #f))
;; (let ((dat (cdr mlst)))
;; (list (car dat) ;; host
;; (string->number (cadr dat)) ;; port
;; (string->number (caddr dat))))))
;; (list #f #f #f))))))))
;;
;; ;; get a list of servers with all relevant data
;; ;; ( mod-time host port start-time pid )
;; ;;
;; (define (server:get-list areapath #!key (limit #f))
;; (let ((fname-rx (regexp "^(|.*/)server-(\\d+)-(\\S+).log$"))
;; (day-seconds (* 24 60 60)))
;; ;; if the directory exists continue to get the list
;; ;; otherwise attempt to create the logs dir and then
;; ;; continue
;; (if (if (directory-exists? (conc areapath "/logs"))
;; '()
;; (if (file-write-access? areapath)
;; (begin
;; (condition-case
;; (create-directory (conc areapath "/logs") #t)
;; (exn (i/o file)(debug:print 0 *default-log-port* "ERROR: Cannot create directory at " (conc areapath "/logs")))
;; (exn ()(debug:print 0 *default-log-port* "ERROR: Unknown error attemtping to get server list.")))
;; (directory-exists? (conc areapath "/logs")))
;; '()))
;; (let* ((server-logs (glob (conc areapath "/logs/server-*.log")))
;; (num-serv-logs (length server-logs)))
;; (if (null? server-logs)
;; '()
;; (let loop ((hed (car server-logs))
;; (tal (cdr server-logs))
;; (res '()))
;; (let* ((mod-time (handle-exceptions
;; exn
;; (current-seconds) ;; 0
;; (file-modification-time hed))) ;; default to *very* old so log gets ignored if deleted
;; (down-time (- (current-seconds) mod-time))
;; (serv-dat (if (or (< num-serv-logs 10)
;; (< down-time 900)) ;; day-seconds))
;; (server:logf-get-start-info hed)
;; '())) ;; don't waste time processing server files not touched in the 15 minutes if there are more than ten servers to look at
;; (serv-rec (cons mod-time serv-dat))
;; (fmatch (string-match fname-rx hed))
;; (pid (if fmatch (string->number (list-ref fmatch 2)) #f))
;; (new-res (if (null? serv-dat)
;; res
;; (cons (append serv-rec (list pid)) res))))
;; (if (null? tal)
;; (if (and limit
;; (> (length new-res) limit))
;; new-res ;; (take new-res limit) <= need intelligent sorting before this will work
;; new-res)
;; (loop (car tal)(cdr tal) new-res)))))))))
;;
;; (define (server:get-num-alive srvlst)
;; (let ((num-alive 0))
;; (for-each
;; (lambda (server)
;; (match-let (((mod-time host port start-time pid)
;; server))
;; (let* ((uptime (- (current-seconds) mod-time))
;; (runtime (if start-time
;; (- mod-time start-time)
;; 0)))
;; (if (< uptime 5)(set! num-alive (+ num-alive 1))))))
;; srvlst)
;; num-alive))
;;
;; ;; given a list of servers get a list of valid servers, i.e. at least
;; ;; 10 seconds old, has started and is less than 1 hour old and is
;; ;; active (i.e. mod-time < 10 seconds
;; ;;
;; ;; mod-time host port start-time pid
;; ;;
;; ;; sort by start-time descending. I.e. get the oldest first. Young servers will thus drop off
;; ;; and servers should stick around for about two hours or so.
;; ;;
;; (define (server:get-best srvlst)
;; (let* ((nums (server:get-num-servers))
;; (now (current-seconds))
;; (slst (sort
;; (filter (lambda (rec)
;; (if (and (list? rec)
;; (> (length rec) 2))
;; (let ((start-time (list-ref rec 3))
;; (mod-time (list-ref rec 0)))
;; ;; (print "start-time: " start-time " mod-time: " mod-time)
;; (and start-time mod-time
;; (> (- now start-time) 0) ;; been running at least 0 seconds
;; (< (- now mod-time) 16) ;; still alive - file touched in last 16 seconds
;; (< (- now start-time)
;; (+ (- (string->number (or (configf:lookup *configdat* "server" "runtime") "3600"))
;; 180)
;; (random 360))) ;; under one hour running time +/- 180
;; ))
;; #f))
;; srvlst)
;; (lambda (a b)
;; (< (list-ref a 3)
;; (list-ref b 3))))))
;; (if (> (length slst) nums)
;; (take slst nums)
;; slst)))
;;
;; (define (server:get-first-best areapath)
;; (let ((srvrs (server:get-best (server:get-list areapath))))
;; (if (and srvrs
;; (not (null? srvrs)))
;; (car srvrs)
;; #f)))
;;
;; (define (server:get-rand-best areapath)
;; (let ((srvrs (server:get-best (server:get-list areapath))))
;; (if (and (list? srvrs)
;; (not (null? srvrs)))
;; (let* ((len (length srvrs))
;; (idx (random len)))
;; (list-ref srvrs idx))
;; #f)))
;;
;;
;; (define (server:record->url servr)
;; (match-let (((mod-time host port start-time pid)
;; servr))
;; (if (and host port)
;; (conc host ":" port)
;; #f)))
;;
;; (define (server:get-client-signature) ;; BB> why is this proc named "get-"? it returns nothing -- set! has not return value.
;; (if *my-client-signature* *my-client-signature*
;; (let ((sig (server:mk-signature)))
;; (set! *my-client-signature* sig)
;; *my-client-signature*)))
;;
;; ;; kind start up of servers, wait 40 seconds before allowing another server for a given
;; ;; run-id to be launched
;; (define (server:kind-run areapath)
;; (if (not (server:check-if-running areapath)) ;; why try if there is already a server running?
;; (let* ((last-run-dat (hash-table-ref/default *server-kind-run* areapath '(0 0))) ;; callnum, whenrun
;; (call-num (car last-run-dat))
;; (when-run (cadr last-run-dat))
;; (run-delay (+ (case call-num
;; ((0) 0)
;; ((1) 20)
;; ((2) 300)
;; (else 600))
;; (random 5))) ;; add a small random number just in case a lot of jobs hit the work hosts simultaneously
;; (lock-file (conc areapath "/logs/server-start.lock")))
;; (if (> (- (current-seconds) when-run) run-delay)
;; (begin
;; (common:simple-file-lock-and-wait lock-file expire-time: 15)
;; (server:run areapath)
;; (thread-sleep! 5) ;; don't release the lock for at least a few seconds
;; (common:simple-file-release-lock lock-file)))
;; (hash-table-set! *server-kind-run* areapath (list (+ call-num 1)(current-seconds))))))
;;
;; (define (server:start-and-wait areapath #!key (timeout 60))
;; (let ((give-up-time (+ (current-seconds) timeout)))
;; (let loop ((server-url (server:check-if-running areapath))
;; (try-num 0))
;; (if (or server-url
;; (> (current-seconds) give-up-time)) ;; server-url will be #f if no server available.
;; server-url
;; (let ((num-ok (length (server:get-best (server:get-list areapath)))))
;; (if (and (> try-num 0) ;; first time through simply wait a little while then try again
;; (< num-ok 1)) ;; if there are no decent candidates for servers then try starting a new one
;; (server:kind-run areapath))
;; (thread-sleep! 5)
;; (loop (server:check-if-running areapath)
;; (+ try-num 1)))))))
;;
;; (define server:try-running server:run) ;; there is no more per-run servers ;; REMOVE ME. BUG.
;;
;; (define (server:get-num-servers #!key (numservers 2))
;; (let ((ns (string->number
;; (or (configf:lookup *configdat* "server" "numservers") "notanumber"))))
;; (or ns numservers)))
;;
;; ;; no longer care if multiple servers are started by accident. older servers will drop off in time.
;; ;;
;; (define (server:check-if-running areapath) ;; #!key (numservers "2"))
;; (let* ((ns (server:get-num-servers))
;; (servers (server:get-best (server:get-list areapath))))
;; ;; (print "servers: " servers " ns: " ns)
;; (if (or (and servers
;; (null? servers))
;; (not servers)
;; (and (list? servers)
;; (< (length servers) (random ns)))) ;; somewhere between 0 and numservers
;; #f
;; (let loop ((hed (car servers))
;; (tal (cdr servers)))
;; (let ((res (server:check-server hed)))
;; (if res
;; res
;; (if (null? tal)
;; #f
;; (loop (car tal)(cdr tal)))))))))
;;
;; ;; ping the given server
;; ;;
;; (define (server:check-server server-record)
;; (let* ((server-url (server:record->url server-record))
;; (res (case *transport-type*
;; ((http)(server:ping server-url))
;; ;; ((nmsg)(nmsg-transport:ping (tasks:hostinfo-get-interface server)
;; )))
;; (if res
;; server-url
;; #f)))
;;
;; ;; DO STUFF HERE - BUG
;; (define (server:kill servr)
;; (match-let (((mod-time hostname port start-time pid)
;; servr))
;; #;(tasks:kill-server hostname pid)
;; #f
;; ))
;;
;; ;; called in megatest.scm, host-port is string hostname:port
;; ;;
;; ;; NOTE: This is NOT called directly from clients as not all transports support a client running
;; ;; in the same process as the server.
;; ;;
;; (define (server:ping host-port-in #!key (do-exit #f))
;; (let ((host:port (if (not host-port-in) ;; use read-dotserver to find
;; #f ;; (server:check-if-running *toppath*)
;; ;; (if (number? host-port-in) ;; we were handed a server-id
;; ;; (let ((srec (tasks:get-server-by-id (db:delay-if-busy (tasks:open-db)) host-port-in)))
;; ;; ;; (print "srec: " srec " host-port-in: " host-port-in)
;; ;; (if srec
;; ;; (conc (vector-ref srec 3) ":" (vector-ref srec 4))
;; ;; (conc "no such server-id " host-port-in)))
;; host-port-in))) ;; )
;; (let* ((host-port (if host:port
;; (let ((slst (string-split host:port ":")))
;; (if (eq? (length slst) 2)
;; (list (car slst)(string->number (cadr slst)))
;; #f))
;; #f)))
;; ;; (toppath (launch:setup)))
;; ;; (print "host-port=" host-port)
;; (if (not host-port)
;; (begin
;; (if host-port-in
;; (debug:print 0 *default-log-port* "ERROR: bad host:port"))
;; (if do-exit (exit 1))
;; #f)
;; (let* ((iface (car host-port))
;; (port (cadr host-port))
;; (server-dat (http-transport:client-connect iface port))
;; (login-res (rmt:login-no-auto-client-setup server-dat)))
;; (if (and (list? login-res)
;; (car login-res))
;; (begin
;; ;; (print "LOGIN_OK")
;; (if do-exit (exit 0))
;; #t)
;; (begin
;; ;; (print "LOGIN_FAILED")
;; (if do-exit (exit 1))
;; #f)))))))
;;
;; ;; run ping in separate process, safest way in some cases
;; ;;
;; (define (server:ping-server ifaceport)
;; (with-input-from-pipe
;; (conc (common:get-megatest-exe) " -ping " ifaceport)
;; (lambda ()
;; (let loop ((inl (read-line))
;; (res "NOREPLY"))
;; (if (eof-object? inl)
;; (case (string->symbol res)
;; ((NOREPLY) #f)
;; ((LOGIN_OK) #t)
;; (else #f))
;; (loop (read-line) inl))))))
;;
;; ;; NOT USED (well, ok, reference in rpc-transport but otherwise not used).
;; ;;
;; (define (server:login toppath)
;; (lambda (toppath)
;; (set! *db-last-access* (current-seconds)) ;; might not be needed.
;; (if (equal? *toppath* toppath)
;; #t
;; #f)))
;;
;; ;; timeout is hms string: 1h 5m 3s, default is 1 minute
;; ;;
;; (define (server:expiration-timeout)
;; (let ((tmo (configf:lookup *configdat* "server" "timeout")))
;; (if (and (string? tmo)
;; (common:hms-string->seconds tmo)) ;; BUG: hms-string->seconds is broken, if given "10" returns 0. Also, it doesn't belong in this logic unless the string->number is changed below
;; (* 3600 (string->number tmo))
;; 60)))
;;
;; (define (server:get-best-guess-address hostname)
;; (let ((res #f))
;; (for-each
;; (lambda (adr)
;; (if (not (eq? (u8vector-ref adr 0) 127))
;; (set! res adr)))
;; ;; NOTE: This can fail when there is no mention of the host in /etc/hosts. FIXME
;; (vector->list (hostinfo-addresses (hostname->hostinfo hostname))))
;; (string-intersperse
;; (map number->string
;; (u8vector->list
;; (if res res (hostname->ip hostname)))) ".")))
;;
;; ;; moving this here as it needs access to db and cannot be in common.
;; ;;
;; (define (server:writable-watchdog dbstruct)
;; (thread-sleep! 0.05) ;; delay for startup
;; (let ((legacy-sync (common:run-sync?))
;; (sync-stale-seconds (configf:lookup-number *configdat* "server" "sync-stale-seconds" default: 300))
;; (debug-mode (debug:debug-mode 1))
;; (last-time (current-seconds))
;; (no-sync-db (db:open-no-sync-db))
;; (sync-duration 0) ;; run time of the sync in milliseconds
;; ;;(this-wd-num (begin (mutex-lock! *wdnum*mutex) (let ((x *wdnum*)) (set! *wdnum* (add1 *wdnum*)) (mutex-unlock! *wdnum*mutex) x)))
;; )
;; (set! *no-sync-db* no-sync-db) ;; make the no sync db available to api calls
;; (debug:print-info 2 *default-log-port* "Periodic sync thread started.")
;; (debug:print-info 3 *default-log-port* "watchdog starting. legacy-sync is " legacy-sync" pid="(current-process-id) );; " this-wd-num="this-wd-num)
;; (if (and legacy-sync (not *time-to-exit*))
;; (let* (;;(dbstruct (db:setup))
;; (mtdb (dbr:dbstruct-mtdb dbstruct))
;; (mtpath (db:dbdat-get-path mtdb))
;; (tmp-area (common:get-db-tmp-area))
;; (start-file (conc tmp-area "/.start-sync"))
;; (end-file (conc tmp-area "/.end-sync")))
;; (debug:print-info 0 *default-log-port* "Server running, periodic sync started.")
;; (let loop ()
;; ;; sync for filesystem local db writes
;; ;;
;; (mutex-lock! *db-multi-sync-mutex*)
;; (let* ((need-sync (>= *db-last-access* *db-last-sync*)) ;; no sync since last write
;; (sync-in-progress *db-sync-in-progress*)
;; (min-intersync-delay (configf:lookup-number *configdat* "server" "minimum-intersync-delay" default: 5))
;; (should-sync (and (not *time-to-exit*)
;; (> (- (current-seconds) *db-last-sync*) min-intersync-delay))) ;; sync every five seconds minimum, deprecated logic, can probably be removed
;; (start-time (current-seconds))
;; (cpu-load-adj (alist-ref 'adj-proc-load (common:get-normalized-cpu-load #f)))
;; (mt-mod-time (file-modification-time mtpath))
;; (last-sync-start (if (common:file-exists? start-file)
;; (file-modification-time start-file)
;; 0))
;; (last-sync-end (if (common:file-exists? end-file)
;; (file-modification-time end-file)
;; 10))
;; (sync-period (+ 3 (* cpu-load-adj 30))) ;; as adjusted load increases increase the sync period
;; (recently-synced (and (< (- start-time mt-mod-time) sync-period) ;; not useful if sync didn't modify megatest.db!
;; (< mt-mod-time last-sync-start)))
;; (sync-done (<= last-sync-start last-sync-end))
;; (sync-stale (> start-time (+ last-sync-start sync-stale-seconds)))
;; (will-sync (and (not *time-to-exit*) ;; do not start a sync if we are in the process of exiting
;; (or need-sync should-sync)
;; (or sync-done sync-stale)
;; (not sync-in-progress)
;; (not recently-synced))))
;; (debug:print-info 13 *default-log-port* "WD writable-watchdog top of loop. need-sync="need-sync" sync-in-progress=" sync-in-progress
;; " should-sync="should-sync" start-time="start-time" mt-mod-time="mt-mod-time" recently-synced="recently-synced" will-sync="will-sync
;; " sync-done=" sync-done " sync-period=" sync-period)
;; (if (and (> sync-period 5)
;; (common:low-noise-print 30 "sync-period"))
;; (debug:print-info 0 *default-log-port* "Increased sync period due to long sync times, sync took: " sync-period " seconds."))
;; ;; (if recently-synced (debug:print-info 0 *default-log-port* "Skipping sync due to recently-synced flag=" recently-synced))
;; ;; (debug:print-info 0 *default-log-port* "need-sync: " need-sync " sync-in-progress: " sync-in-progress " should-sync: " should-sync " will-sync: " will-sync)
;; (if will-sync (set! *db-sync-in-progress* #t))
;; (mutex-unlock! *db-multi-sync-mutex*)
;; (if will-sync
;; (let (;; (max-sync-duration (configf:lookup-number *configdat* "server" "max-sync-duration")) ;; KEEPING THIS AVAILABLE BUT SHOULD NOT USE, I'M PRETTY SURE IT DOES NOT WORK!
;; (sync-start (current-milliseconds)))
;; (with-output-to-file start-file (lambda ()(print (current-process-id))))
;;
;; ;; put lock here
;;
;; ;; (if (or (not max-sync-duration)
;; ;; (< sync-duration max-sync-duration)) ;; NOTE: db:sync-to-megatest.db keeps track of time of last sync and syncs incrementally
;; (let ((res (db:sync-to-megatest.db dbstruct no-sync-db: no-sync-db))) ;; did we sync any data? If so need to set the db touched flag to keep the server alive
;; (set! sync-duration (- (current-milliseconds) sync-start))
;; (if (> res 0) ;; some records were transferred, keep the db alive
;; (begin
;; (mutex-lock! *heartbeat-mutex*)
;; (set! *db-last-access* (current-seconds))
;; (mutex-unlock! *heartbeat-mutex*)
;; (debug:print-info 0 *default-log-port* "sync called, " res " records transferred."))
;; (debug:print-info 2 *default-log-port* "sync called but zero records transferred")))))
;; ;; ;; TODO: factor this next routine out into a function
;; ;; (with-input-from-pipe ;; this should not block other threads but need to verify this
;; ;; (conc "megatest -sync-to-megatest.db -m testsuite:" (common:get-area-name) ":" *toppath*)
;; ;; (lambda ()
;; ;; (let loop ((inl (read-line))
;; ;; (res #f))
;; ;; (if (eof-object? inl)
;; ;; (begin
;; ;; (set! sync-duration (- (current-milliseconds) sync-start))
;; ;; (cond
;; ;; ((not res)
;; ;; (debug:print 0 *default-log-port* "ERROR: sync from /tmp db to megatest.db appears to have failed. Recommended that you stop your runs and run \"megatest -cleanup-db\""))
;; ;; ((> res 0)
;; ;; (mutex-lock! *heartbeat-mutex*)
;; ;; (set! *db-last-access* (current-seconds))
;; ;; (mutex-unlock! *heartbeat-mutex*))))
;; ;; (let ((num-synced (let ((matches (string-match "^Synced (\\d+).*$" inl)))
;; ;; (if matches
;; ;; (string->number (cadr matches))
;; ;; #f))))
;; ;; (loop (read-line)
;; ;; (or num-synced res))))))))))
;; (if will-sync
;; (begin
;; (mutex-lock! *db-multi-sync-mutex*)
;; (set! *db-sync-in-progress* #f)
;; (set! *db-last-sync* start-time)
;; (with-output-to-file end-file (lambda ()(print (current-process-id))))
;;
;; ;; release lock here
;;
;; (mutex-unlock! *db-multi-sync-mutex*)))
;; (if (and debug-mode
;; (> (- start-time last-time) 60))
;; (begin
;; (set! last-time start-time)
;; (debug:print-info 4 *default-log-port* "timestamp -> " (seconds->time-string (current-seconds)) ", time since start -> " (seconds->hr-min-sec (- (current-seconds) *time-zero*))))))
;;
;; ;; keep going unless time to exit
;; ;;
;; (if (not *time-to-exit*)
;; (let delay-loop ((count 0))
;; ;;(debug:print-info 13 *default-log-port* "delay-loop top; count="count" pid="(current-process-id)" this-wd-num="this-wd-num" *time-to-exit*="*time-to-exit*)
;;
;; (if (and (not *time-to-exit*)
;; (< count 6)) ;; was 11, changing to 4.
;; (begin
;; (thread-sleep! 1)
;; (delay-loop (+ count 1))))
;; (if (not *time-to-exit*) (loop))))
;; ;; time to exit, close the no-sync db here
;; (db:no-sync-close-db no-sync-db)
;; (if (common:low-noise-print 30)
;; (debug:print-info 0 *default-log-port* "Exiting watchdog timer, *time-to-exit* = " *time-to-exit*" pid="(current-process-id) ))))))) ;;" this-wd-num="this-wd-num)))))))
;;