aboutsummaryrefslogtreecommitdiffstats
path: root/lua/mason-core/installer/InstallHandle.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/mason-core/installer/InstallHandle.lua')
-rw-r--r--lua/mason-core/installer/InstallHandle.lua228
1 files changed, 228 insertions, 0 deletions
diff --git a/lua/mason-core/installer/InstallHandle.lua b/lua/mason-core/installer/InstallHandle.lua
new file mode 100644
index 00000000..f5a42c53
--- /dev/null
+++ b/lua/mason-core/installer/InstallHandle.lua
@@ -0,0 +1,228 @@
+local EventEmitter = require "mason-core.EventEmitter"
+local Optional = require "mason-core.optional"
+local _ = require "mason-core.functional"
+local a = require "mason-core.async"
+local log = require "mason-core.log"
+local platform = require "mason-core.platform"
+local process = require "mason-core.process"
+local spawn = require "mason-core.spawn"
+
+local uv = vim.loop
+
+---@alias InstallHandleState
+--- | '"IDLE"'
+--- | '"QUEUED"'
+--- | '"ACTIVE"'
+--- | '"CLOSED"'
+
+---@class InstallHandleSpawnHandle
+---@field uv_handle luv_handle
+---@field pid integer
+---@field cmd string
+---@field args string[]
+local InstallHandleSpawnHandle = {}
+InstallHandleSpawnHandle.__index = InstallHandleSpawnHandle
+
+---@param luv_handle luv_handle
+---@param pid integer
+---@param cmd string
+---@param args string[]
+function InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)
+ ---@type InstallHandleSpawnHandle
+ local instance = {}
+ setmetatable(instance, InstallHandleSpawnHandle)
+ instance.uv_handle = luv_handle
+ instance.pid = pid
+ instance.cmd = cmd
+ instance.args = args
+ return instance
+end
+
+function InstallHandleSpawnHandle:__tostring()
+ return ("%s %s"):format(self.cmd, table.concat(self.args, " "))
+end
+
+---@class InstallHandle : EventEmitter
+---@field package AbstractPackage
+---@field state InstallHandleState
+---@field stdio { buffers: { stdout: string[], stderr: string[] }, sink: StdioSink }
+---@field is_terminated boolean
+---@field location InstallLocation
+---@field private spawn_handles InstallHandleSpawnHandle[]
+local InstallHandle = {}
+InstallHandle.__index = InstallHandle
+setmetatable(InstallHandle, { __index = EventEmitter })
+
+---@param handle InstallHandle
+local function new_sink(handle)
+ local stdout, stderr = {}, {}
+ return {
+ buffers = { stdout = stdout, stderr = stderr },
+ sink = {
+ stdout = function(chunk)
+ stdout[#stdout + 1] = chunk
+ handle:emit("stdout", chunk)
+ end,
+ stderr = function(chunk)
+ stderr[#stderr + 1] = chunk
+ handle:emit("stderr", chunk)
+ end,
+ },
+ }
+end
+
+---@param pkg AbstractPackage
+---@param location InstallLocation
+function InstallHandle:new(pkg, location)
+ ---@type InstallHandle
+ local instance = EventEmitter.new(self)
+ instance.state = "IDLE"
+ instance.package = pkg
+ instance.spawn_handles = {}
+ instance.stdio = new_sink(instance)
+ instance.is_terminated = false
+ instance.location = location
+ return instance
+end
+
+---@param luv_handle luv_handle
+---@param pid integer
+---@param cmd string
+---@param args string[]
+function InstallHandle:register_spawn_handle(luv_handle, pid, cmd, args)
+ local spawn_handles = InstallHandleSpawnHandle:new(luv_handle, pid, cmd, args)
+ log.fmt_trace("Pushing spawn_handles stack for %s: %s (pid: %s)", self, spawn_handles, pid)
+ self.spawn_handles[#self.spawn_handles + 1] = spawn_handles
+ self:emit "spawn_handles:change"
+end
+
+---@param luv_handle luv_handle
+function InstallHandle:deregister_spawn_handle(luv_handle)
+ for i = #self.spawn_handles, 1, -1 do
+ if self.spawn_handles[i].uv_handle == luv_handle then
+ log.fmt_trace("Popping spawn_handles stack for %s: %s", self, self.spawn_handles[i])
+ table.remove(self.spawn_handles, i)
+ self:emit "spawn_handles:change"
+ return true
+ end
+ end
+ return false
+end
+
+---@return Optional # Optional<InstallHandleSpawnHandle>
+function InstallHandle:peek_spawn_handle()
+ return Optional.of_nilable(self.spawn_handles[#self.spawn_handles])
+end
+
+function InstallHandle:is_idle()
+ return self.state == "IDLE"
+end
+
+function InstallHandle:is_queued()
+ return self.state == "QUEUED"
+end
+
+function InstallHandle:is_active()
+ return self.state == "ACTIVE"
+end
+
+function InstallHandle:is_closed()
+ return self.state == "CLOSED"
+end
+
+function InstallHandle:is_closing()
+ return self:is_closed() or self.is_terminated
+end
+
+---@param new_state InstallHandleState
+function InstallHandle:set_state(new_state)
+ local old_state = self.state
+ self.state = new_state
+ log.fmt_trace("Changing %s state from %s to %s", self, old_state, new_state)
+ self:emit("state:change", new_state, old_state)
+end
+
+---@param signal integer
+function InstallHandle:kill(signal)
+ assert(not self:is_closed(), "Cannot kill closed handle.")
+ log.fmt_trace("Sending signal %s to luv handles in %s", signal, self)
+ for _, spawn_handles in pairs(self.spawn_handles) do
+ process.kill(spawn_handles.uv_handle, signal)
+ end
+ self:emit("kill", signal)
+end
+
+---@param pid integer
+local win_taskkill = a.scope(function(pid)
+ spawn.taskkill {
+ "/f",
+ "/t",
+ "/pid",
+ pid,
+ }
+end)
+
+function InstallHandle:terminate()
+ assert(not self:is_closed(), "Cannot terminate closed handle.")
+ if self.is_terminated then
+ log.fmt_trace("Handle is already terminated %s", self)
+ return
+ end
+ log.fmt_trace("Terminating %s", self)
+ -- https://github.com/libuv/libuv/issues/1133
+ if platform.is.win then
+ for _, spawn_handles in ipairs(self.spawn_handles) do
+ win_taskkill(spawn_handles.pid)
+ end
+ else
+ self:kill(15) -- SIGTERM
+ end
+ self.is_terminated = true
+ self:emit "terminate"
+ local check = uv.new_check()
+ check:start(function()
+ for _, spawn_handles in ipairs(self.spawn_handles) do
+ local luv_handle = spawn_handles.uv_handle
+ local ok, is_closing = pcall(luv_handle.is_closing, luv_handle)
+ if ok and not is_closing then
+ return
+ end
+ end
+ check:stop()
+ if not self:is_closed() then
+ self:close()
+ end
+ end)
+end
+
+function InstallHandle:queued()
+ assert(self:is_idle(), "Can only queue idle handles.")
+ self:set_state "QUEUED"
+end
+
+function InstallHandle:active()
+ assert(self:is_idle() or self:is_queued(), "Can only activate idle or queued handles.")
+ self:set_state "ACTIVE"
+end
+
+function InstallHandle:close()
+ log.fmt_trace("Closing %s", self)
+ assert(not self:is_closed(), "Handle is already closed.")
+ for _, spawn_handles in ipairs(self.spawn_handles) do
+ local luv_handle = spawn_handles.uv_handle
+ local ok, is_closing = pcall(luv_handle.is_closing, luv_handle)
+ if ok then
+ assert(is_closing, "There are open libuv handles.")
+ end
+ end
+ self.spawn_handles = {}
+ self:set_state "CLOSED"
+ self:emit "closed"
+ self:__clear_event_handlers()
+end
+
+function InstallHandle:__tostring()
+ return ("InstallHandle(package=%s, state=%s)"):format(self.package, self.state)
+end
+
+return InstallHandle