;; phpinspect-worker.el --- PHP parsing and completion package -*- lexical-binding: t; -*- ;; Copyright (C) 2021-2023 Free Software Foundation, Inc ;; Author: Hugo Thunnissen ;; 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 . ;;; 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