You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
phpinspect.el/phpinspect-worker.el

251 lines
10 KiB
EmacsLisp

;; phpinspect-worker.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program 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.
;; This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'phpinspect-util)
(require 'phpinspect-project-struct)
(require 'phpinspect-index)
(require 'phpinspect-class)
(require 'phpinspect-queue)
(require 'phpinspect-pipeline)
(eval-when-compile
(phpinspect--declare-log-group 'worker))
(defcustom phpinspect-worker-pause-time 1
"Number of seconds that `phpinspect-worker' should pause when
user input is detected. A higher value means better
responsiveness, at the cost of slower code indexation. On modern
hardware this probably doesn't need to be tweaked."
:type 'number
:group 'phpinspect)
(defvar phpinspect-worker nil
"Contains the phpinspect worker that is used by all projects.")
(cl-defstruct (phpinspect-worker
(:constructor phpinspect-make-worker-generated))
(queue nil
:type phpinspect-queue-item
:documentation
"The queue of tasks that are pending")
(thread nil
:type thread
:documentation
"The thread of this worker")
(continue-running nil
:type bool
:documentation
"Whether or not the thread should continue
running. If this is nil, the thread is stopped.")
(skip-next-pause nil
:type bool
:documentation
"Whether or not the thread should skip its next scheduled pause."))
(cl-defstruct (phpinspect-dynamic-worker
(:constructor phpinspect-make-dynamic-worker-generated))
"A dynamic worker is nothing other than an object that is
supported by all of the same methods as a `phpinspect-worker`,
but relies on an underlying, global worker to actually do the
work. The reason for its implementation is to allow users to
manage phpinspect's worker thread centrally in a dynamic
variable, while also making the behaviour of objects that depend
on the worker independent of dynamic variables during testing.")
(cl-defmethod phpinspect-resolve-dynamic-worker ((_worker phpinspect-dynamic-worker))
phpinspect-worker)
(defun phpinspect-make-dynamic-worker ()
(phpinspect-make-dynamic-worker-generated))
(defsubst phpinspect-make-worker ()
"Create a new worker object."
(let ((worker (phpinspect-make-worker-generated)))
(setf (phpinspect-worker-queue worker)
(phpinspect-make-queue (phpinspect-worker-make-wakeup-function worker)))
worker))
(define-error 'phpinspect-wakeup-thread
"This error is used to wakeup the index thread")
(cl-defgeneric phpinspect-worker-make-wakeup-function (worker)
"Create a function that can be used to wake up WORKER's thread.")
(cl-defmethod phpinspect-worker-wakeup ((worker phpinspect-worker))
(when (eq main-thread (thread--blocker (phpinspect-worker-thread worker)))
(phpinspect--log "Attempting to wakeup worker thread")
(thread-signal (phpinspect-worker-thread worker)
'phpinspect-wakeup-thread nil)))
(cl-defmethod phpinspect-worker-make-wakeup-function ((worker phpinspect-worker))
(lambda ()
(phpinspect-worker-wakeup worker)))
(cl-defmethod phpinspect-worker-make-wakeup-function ((worker phpinspect-dynamic-worker))
(phpinspect-worker-make-wakeup-function (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-live-p (worker)
"Just a shorthand to check whether or not the WORKER's thread is running.")
(cl-defmethod phpinspect-worker-live-p ((worker phpinspect-worker))
(when (phpinspect-worker-thread worker)
(thread-live-p (phpinspect-worker-thread worker))))
(cl-defmethod phpinspect-worker-live-p ((worker phpinspect-dynamic-worker))
(phpinspect-worker-live-p (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-enqueue (worker task)
"Enqueue a TASK to be executed by WORKER.")
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker) task)
"Specialized enqueuement method for index tasks. Prevents
indexation tasks from being added when there are identical tasks
already present in the queue."
(phpinspect--log "Enqueuing task")
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-task=))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-dynamic-worker) task)
(phpinspect-worker-enqueue (phpinspect-resolve-dynamic-worker worker)
task))
(cl-defgeneric phpinspect-worker-make-thread-function (worker)
"Create a function that can be used to start WORKER's thread.")
(defun phpinspect--worker-pause ()
(let* ((mx (make-mutex))
(continue (make-condition-variable mx)))
(phpinspect-thread-pause phpinspect-worker-pause-time mx continue)))
(cl-defmethod phpinspect-worker-make-thread-function ((worker phpinspect-worker))
(lambda ()
(while (phpinspect-worker-continue-running worker)
;; This error is used to wake up the thread when new tasks are added to the
;; queue.
(condition-case err
(progn
(phpinspect--log "Dequeueing next task")
(ignore-error phpinspect-wakeup-thread
;; Prevent quitting during tasks, as this can break data integrity
(let* ((inhibit-quit t)
(task (phpinspect-queue-dequeue (phpinspect-worker-queue worker))))
(if task
;; Execute task if it belongs to a project that has not been
;; purged (meaning that it is still actively used).
(if (phpinspect-project-purged (phpinspect-task-project task))
(phpinspect--log "Projecthas been purged. Skipping task")
(phpinspect--log "Executing task")
(phpinspect-task-execute task worker))
;; else: join with the main thread until wakeup is signaled
(phpinspect--log "No tasks, joining main thread")
(thread-join main-thread))))
;; Pause for a second after indexing something, to allow user input to
;; interrupt the thread.
(unless (or (not (phpinspect--input-pending-p))
(phpinspect-worker-skip-next-pause worker))
(phpinspect--worker-pause))
(setf (phpinspect-worker-skip-next-pause worker) nil))
(quit (ignore-error phpinspect-wakeup-thread
(phpinspect--worker-pause)))
(phpinspect-wakeup-thread)
((debug error) (thread-signal main-thread 'phpinspect-worker-error err))
(t (phpinspect--log "Phpinspect worker thread errored :%s" err))))
(phpinspect--log "Worker thread exiting")
(phpinspect-message "phpinspect worker thread exited")))
(cl-defmethod phpinspect-worker-make-thread-function ((worker phpinspect-dynamic-worker))
(phpinspect-worker-make-thread-function
(phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-start (worker)
"Start WORKER's thread.")
(cl-defmethod phpinspect-worker-start ((worker phpinspect-worker))
(if (phpinspect-worker-live-p worker)
(error "Attempt to start a worker that is already running")
(progn
(setf (phpinspect-worker-continue-running worker) t)
(setf (phpinspect-worker-thread worker)
;; Use with-temp-buffer so as to not associate thread with the
;; current buffer. Otherwise, the buffer associated with this thread
;; will be unkillable while the thread is running.
(with-temp-buffer
(make-thread (phpinspect-worker-make-thread-function worker) "phpinspect-worker"))))))
(cl-defmethod phpinspect-worker-start ((worker phpinspect-dynamic-worker))
(phpinspect-worker-start (phpinspect-resolve-dynamic-worker worker)))
(cl-defgeneric phpinspect-worker-stop (worker)
"Stop the worker")
(cl-defmethod phpinspect-worker-stop ((worker phpinspect-worker))
(setf (phpinspect-worker-continue-running worker) nil)
(phpinspect-worker-wakeup worker))
(cl-defmethod phpinspect-worker-stop ((worker phpinspect-dynamic-worker))
(phpinspect-worker-stop (phpinspect-resolve-dynamic-worker worker)))
(defun phpinspect-ensure-worker ()
(interactive)
(when (not phpinspect-worker)
(setq phpinspect-worker (phpinspect-make-worker)))
(when (not (phpinspect-worker-live-p phpinspect-worker))
(phpinspect-worker-start phpinspect-worker)))
(defun phpinspect-stop-worker ()
(interactive)
(phpinspect-worker-stop phpinspect-worker))
;;; TASKS
;; The rest of this file contains task definitions. Tasks represent actions that
;; can be executed by `phpinspect-worker'. Some methods are required to be
;; implemented for all tasks, while others aren't.
;; REQUIRED METHODS:
;; - phpinspect-task-execute
;; - phpinspect-task-project
;; OPTIONAL METHODS:
;; - phpinspect-task=
;;; Code:
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(cl-defmethod phpinspect-task= (_task1 _task2)
"Whether or not TASK1 and TASK2 are set to execute the exact same action."
nil)
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
(cl-defmethod phpinspect-worker-enqueue ((_worker (eql 'nil-worker)) &rest _ignored))
(cl-defmethod phpinspect-worker-live-p ((_worker (eql 'nil-worker)) &rest _ignored) t)
(provide 'phpinspect-worker)
;;; phpinspect-worker.el ends here