;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; Package: modlisp -*- ;;;; ************************************************************************* ;;;; FILE IDENTIFICATION ;;;; ;;;; Name: listener.lisp ;;;; Purpose: Listener and worker processes ;;;; Programmer: Kevin M. Rosenberg ;;;; Date Started: Dec 2002 ;;;; ;;;; $Id: listener.lisp,v 1.2 2003/07/09 22:12:52 kevin Exp $ ;;;; ************************************************************************* (in-package #:kmrcl) ;;; Variables and data structures for Listener (defvar *listener-count* 0 "used to name listeners") (defvar *worker-count* 0 "used to name workers") (defvar *active-listeners* nil "List of active listeners") (defclass listener () ((port :initarg :port :accessor port) (function :initarg :function :accessor listener-function :initform nil) (function-args :initarg :function-args :accessor function-args :initform nil) (process :initarg :process :accessor process) (socket :initarg :socket :accessor socket) (workers :initform nil :accessor workers :documentation "list of worker threads") (name :initform "" :accessor name :initarg :name) (base-name :initform "listener" :accessor base-name :initarg :base-name) (wait :initform nil :accessor wait :initarg :wait) (catch-errors :initform nil :accessor catch-errors :initarg :catch-errors) (format :initform :text :accessor listener-format :initarg :format))) (defclass worker () ((listener :initarg :listener :accessor listener :initform nil) (connection :initarg :connection :accessor connection :initform nil) (name :initarg :name :accessor name :initform nil) (thread-fun :initarg :thread-fun :accessor thread-fun :initform nil) (process :initarg :process :accessor process :initform nil))) (defmethod print-object ((obj listener) s) (print-unreadable-object (obj s :type t :identity nil) (format s "port ~A" (port obj)))) (defmethod print-object ((obj worker) s) (print-unreadable-object (obj s :type t :identity nil) (format s "port ~A" (port (listener obj))))) ;; High-level API (defun init/listener (listener state) (check-type listener listener) (case state (:start (when (member listener *active-listeners*) (warn "~&listener already started") (return-from init/listener listener)) (handler-case (progn (setf (name listener) (next-server-name (base-name listener))) (make-socket-server listener)) (error (e) (format t "~&Error while trying to start listener~& ~A" e) (decf *listener-count*) nil) (:no-error (res) (declare (ignore res)) (push listener *active-listeners*) listener))) (:stop (unless (member listener *active-listeners*) (warn "~&listener is not in active list") (return-from init/listener listener)) (dolist (worker (workers listener)) (close-active-socket (connection worker)) (destroy-process (process worker)) (setf (connection worker) nil) (setf (process worker) nil)) (setf (workers listener) nil) (with-slots (process socket) listener (errorset (close-passive-socket socket) t) (errorset (destroy-process process) t) (setf process nil) (setf socket nil)) (setq *active-listeners* (remove listener *active-listeners*))) (:restart (init/listener listener :stop) (init/listener listener :start)))) (defun stop-all/listener () (dolist (listener *active-listeners*) (ignore-errors (init/listener listener :stop)))) ;; Low-level functions (defun next-server-name (base-name) (format nil "~A-socket-server-~D" base-name (incf *listener-count*))) (defun next-worker-name (base-name) (format nil "~A-worker-~D" base-name (incf *worker-count*))) (defun make-socket-server (listener) (setf (socket listener) (create-inet-listener (port listener) :format (listener-format listener))) (setf (process listener) (make-process (name listener) #'(lambda () (start-socket-server listener)))) listener) (defmethod initialize-instance :after ((self worker) &key listener connection name &allow-other-keys) (unless connection (error "connection not provided to modlisp-worker")) (setf (slot-value self 'listener) listener) (setf (slot-value self 'name) name) (setf (slot-value self 'connection) connection) (setf (slot-value self 'thread-fun) #'(lambda () (unwind-protect (if (catch-errors listener) (handler-case (apply (listener-function listener) connection (function-args listener)) (error (e) (cmsg "Error ~A [~A]" e name))) (apply (listener-function listener) connection (function-args listener))) (progn (errorset (close-active-socket connection) nil) (cmsg-c :threads "~A ended" name) (setf (workers listener) (remove self (workers listener)))))))) (defun start-socket-server (listener) (unwind-protect (loop (let ((connection (accept-tcp-connection (socket listener)))) (if (wait listener) (unwind-protect (apply (listener-function listener) connection (function-args listener)) (errorset (close connection) nil)) (let ((worker (make-instance 'worker :listener listener :connection connection :name (next-worker-name (base-name listener))))) (setf (process worker) (make-process (name worker) (thread-fun worker))) (push worker (workers listener)))))) (errorset (close-passive-socket (socket listener)) nil)))