makit 0.0.172 → 0.0.173
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/makit/port.rb +180 -0
- data/lib/makit/port_utility.rb +7 -118
- data/lib/makit/version.rb +1 -1
- data/lib/makit.rb +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08a43e1b5e0220843cd50b9e0bc225a7f735a2aba7d7b4af14588515f78aea51'
|
|
4
|
+
data.tar.gz: 6e8cee30fb685c771dbb84bc28fe35eb02526d70f66386ecf368567d0dc55743
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84923a1df4d918af850b09c9ebc110ba6cea43b1e405c173bed5b1252e13ba17cad9c7c79939cc0bb900400a2c54b57d733cc5ba9df83aee6aa28b9dff450613
|
|
7
|
+
data.tar.gz: e9280746a278890230747f65cbc1266e24d1d3428a5ef5a9adef6891c20174da3a7c207831d1db3a10275d87cf2eddf024875f11d3b7c0e226bfe6376a1c92b5
|
data/lib/makit/port.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
3
5
|
# This module provides classes for the Makit gem.
|
|
4
6
|
module Makit
|
|
5
7
|
# This class provide methods for working with ports
|
|
6
8
|
#
|
|
7
9
|
class Port
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
8
12
|
def self.is_port_available?(port)
|
|
9
13
|
socket = Socket.new(:INET, :STREAM)
|
|
10
14
|
socket.bind(Socket.sockaddr_in(port, ""))
|
|
@@ -28,5 +32,181 @@ module Makit
|
|
|
28
32
|
def self.get_random_available_port
|
|
29
33
|
get_available_port(get_random_port)
|
|
30
34
|
end
|
|
35
|
+
|
|
36
|
+
# Ensures a port is available by killing any process using it, or finding the next available port.
|
|
37
|
+
#
|
|
38
|
+
# @param port [Integer] The port to ensure is available
|
|
39
|
+
# @param force [Boolean] If true (default), retries up to 3 times to kill processes on the port.
|
|
40
|
+
# If false, does not kill; finds and returns the next available port starting from +port+.
|
|
41
|
+
# @return [Integer] The port number (guaranteed to be available)
|
|
42
|
+
# @raise [Makit::Port::Error] If force is true and the port is still in use after 3 attempts
|
|
43
|
+
#
|
|
44
|
+
# @example Force free the port (default)
|
|
45
|
+
# Makit::Port.ensure_available(5261) # => 5261
|
|
46
|
+
# Makit::Port.ensure_available(5261, force: true)
|
|
47
|
+
#
|
|
48
|
+
# @example Find next available port without killing
|
|
49
|
+
# Makit::Port.ensure_available(5261, force: false) # => 5261 or next free port
|
|
50
|
+
#
|
|
51
|
+
def self.ensure_available(port, force: true)
|
|
52
|
+
raise ArgumentError, "port must be a positive integer" unless port.is_a?(Integer) && port > 0
|
|
53
|
+
|
|
54
|
+
return port unless port_in_use?(port)
|
|
55
|
+
|
|
56
|
+
if force
|
|
57
|
+
max_retries = 3
|
|
58
|
+
retries = 0
|
|
59
|
+
|
|
60
|
+
while port_in_use?(port) && retries < max_retries
|
|
61
|
+
kill_process_on_port(port)
|
|
62
|
+
sleep(0.5)
|
|
63
|
+
retries += 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if port_in_use?(port)
|
|
67
|
+
raise Error, "Port #{port} is still in use after #{max_retries} attempts to free it"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return port
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
find_available_port(port)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Ensures a port is free by killing any process using it.
|
|
77
|
+
#
|
|
78
|
+
# Examples:
|
|
79
|
+
# Makit::Port.ensure_port_free(5261)
|
|
80
|
+
# Makit::Port.ensure_port_free(5261, verbose: true)
|
|
81
|
+
#
|
|
82
|
+
# Returns true if the port is now free, false if it couldn't be freed.
|
|
83
|
+
#
|
|
84
|
+
def self.ensure_port_free(port, verbose: false)
|
|
85
|
+
raise ArgumentError, "port must be a positive integer" unless port.is_a?(Integer) && port > 0
|
|
86
|
+
|
|
87
|
+
return true unless port_in_use?(port)
|
|
88
|
+
|
|
89
|
+
puts "Port #{port} is in use. Attempting to free it..." if verbose
|
|
90
|
+
|
|
91
|
+
killed = kill_process_on_port(port, verbose: verbose)
|
|
92
|
+
|
|
93
|
+
if killed
|
|
94
|
+
# Wait a moment for the port to be released
|
|
95
|
+
sleep(1)
|
|
96
|
+
# Verify port is now free
|
|
97
|
+
unless port_in_use?(port)
|
|
98
|
+
puts "Port #{port} is now free." if verbose
|
|
99
|
+
return true
|
|
100
|
+
else
|
|
101
|
+
puts "Warning: Port #{port} is still in use after killing process." if verbose
|
|
102
|
+
return false
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
puts "Warning: Could not kill process on port #{port}." if verbose
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
rescue Error, ArgumentError => e
|
|
109
|
+
puts "Error freeing port #{port}: #{e.message}" if verbose
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# -----------------------
|
|
114
|
+
# Internals
|
|
115
|
+
# -----------------------
|
|
116
|
+
|
|
117
|
+
def self.port_in_use?(port)
|
|
118
|
+
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
119
|
+
# Windows: Use netstat
|
|
120
|
+
stdout, _stderr, status = Open3.capture3("netstat", "-ano")
|
|
121
|
+
return false unless status.success?
|
|
122
|
+
stdout.lines.any? { |line| line.include?(":#{port}") && line.include?("LISTENING") }
|
|
123
|
+
else
|
|
124
|
+
# Unix/macOS: Use lsof
|
|
125
|
+
stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
|
|
126
|
+
status.success? && !stdout.strip.empty?
|
|
127
|
+
end
|
|
128
|
+
rescue Errno::ENOENT
|
|
129
|
+
# Command not found - assume port is not in use
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
private_class_method :port_in_use?
|
|
133
|
+
|
|
134
|
+
def self.kill_process_on_port(port, verbose: false)
|
|
135
|
+
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
136
|
+
# Windows: Use netstat to find PID, then taskkill
|
|
137
|
+
stdout, _stderr, status = Open3.capture3("netstat", "-ano")
|
|
138
|
+
return false unless status.success?
|
|
139
|
+
|
|
140
|
+
pids = []
|
|
141
|
+
stdout.lines.each do |line|
|
|
142
|
+
if line.include?(":#{port}") && line.include?("LISTENING")
|
|
143
|
+
parts = line.split
|
|
144
|
+
pid = parts.last
|
|
145
|
+
pids << pid if pid && pid.match?(/^\d+$/)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
killed_any = false
|
|
150
|
+
pids.uniq.each do |pid|
|
|
151
|
+
begin
|
|
152
|
+
stdout, _stderr, status = Open3.capture3("taskkill", "/F", "/PID", pid)
|
|
153
|
+
if status.success?
|
|
154
|
+
puts "Killed process #{pid} on port #{port}" if verbose
|
|
155
|
+
killed_any = true
|
|
156
|
+
end
|
|
157
|
+
rescue Errno::ENOENT
|
|
158
|
+
# taskkill not found
|
|
159
|
+
return false
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
killed_any
|
|
164
|
+
else
|
|
165
|
+
# Unix/macOS: Use lsof to find PID, then kill
|
|
166
|
+
stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
|
|
167
|
+
return false unless status.success?
|
|
168
|
+
return true if stdout.strip.empty? # Port is already free
|
|
169
|
+
|
|
170
|
+
pids = stdout.strip.split("\n").map(&:strip).reject(&:empty?)
|
|
171
|
+
return false if pids.empty?
|
|
172
|
+
|
|
173
|
+
killed_any = false
|
|
174
|
+
pids.each do |pid|
|
|
175
|
+
begin
|
|
176
|
+
stdout, _stderr, status = Open3.capture3("kill", "-9", pid)
|
|
177
|
+
if status.success?
|
|
178
|
+
puts "Killed process #{pid} on port #{port}" if verbose
|
|
179
|
+
killed_any = true
|
|
180
|
+
end
|
|
181
|
+
rescue Errno::ENOENT
|
|
182
|
+
# kill not found
|
|
183
|
+
return false
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
killed_any
|
|
188
|
+
end
|
|
189
|
+
rescue Errno::ENOENT
|
|
190
|
+
# Command not found
|
|
191
|
+
false
|
|
192
|
+
end
|
|
193
|
+
private_class_method :kill_process_on_port
|
|
194
|
+
|
|
195
|
+
def self.find_available_port(start_port, max_attempts = 100)
|
|
196
|
+
port = start_port
|
|
197
|
+
attempts = 0
|
|
198
|
+
|
|
199
|
+
while port_in_use?(port) && attempts < max_attempts
|
|
200
|
+
port += 1
|
|
201
|
+
attempts += 1
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if attempts >= max_attempts
|
|
205
|
+
raise Error, "Could not find available port starting from #{start_port}"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
port
|
|
209
|
+
end
|
|
210
|
+
private_class_method :find_available_port
|
|
31
211
|
end
|
|
32
212
|
end
|
data/lib/makit/port_utility.rb
CHANGED
|
@@ -1,128 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "port"
|
|
4
4
|
|
|
5
5
|
module Makit
|
|
6
|
+
# @deprecated Use {Makit::Port} instead. This module delegates to Makit::Port for backward compatibility.
|
|
6
7
|
module PortUtility
|
|
7
|
-
|
|
8
|
+
Error = Makit::Port::Error
|
|
8
9
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# Examples:
|
|
12
|
-
# Makit::PortUtility.ensure_port_free(5261)
|
|
13
|
-
# Makit::PortUtility.ensure_port_free(5261, verbose: true)
|
|
14
|
-
#
|
|
15
|
-
# Returns true if the port is now free, false if it couldn't be freed.
|
|
16
|
-
#
|
|
10
|
+
# @see Makit::Port.ensure_port_free
|
|
17
11
|
def self.ensure_port_free(port, verbose: false)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
puts "Port #{port} is in use. Attempting to free it..." if verbose
|
|
23
|
-
|
|
24
|
-
killed = kill_process_on_port(port, verbose: verbose)
|
|
25
|
-
|
|
26
|
-
if killed
|
|
27
|
-
# Wait a moment for the port to be released
|
|
28
|
-
sleep(1)
|
|
29
|
-
# Verify port is now free
|
|
30
|
-
unless port_in_use?(port)
|
|
31
|
-
puts "Port #{port} is now free." if verbose
|
|
32
|
-
return true
|
|
33
|
-
else
|
|
34
|
-
puts "Warning: Port #{port} is still in use after killing process." if verbose
|
|
35
|
-
return false
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
puts "Warning: Could not kill process on port #{port}." if verbose
|
|
39
|
-
return false
|
|
40
|
-
end
|
|
41
|
-
rescue Error, ArgumentError => e
|
|
42
|
-
puts "Error freeing port #{port}: #{e.message}" if verbose
|
|
43
|
-
false
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# -----------------------
|
|
47
|
-
# Internals
|
|
48
|
-
# -----------------------
|
|
49
|
-
|
|
50
|
-
def self.port_in_use?(port)
|
|
51
|
-
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
52
|
-
# Windows: Use netstat
|
|
53
|
-
stdout, _stderr, status = Open3.capture3("netstat", "-ano")
|
|
54
|
-
return false unless status.success?
|
|
55
|
-
stdout.lines.any? { |line| line.include?(":#{port}") && line.include?("LISTENING") }
|
|
56
|
-
else
|
|
57
|
-
# Unix/macOS: Use lsof
|
|
58
|
-
stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
|
|
59
|
-
status.success? && !stdout.strip.empty?
|
|
60
|
-
end
|
|
61
|
-
rescue Errno::ENOENT
|
|
62
|
-
# Command not found - assume port is not in use
|
|
63
|
-
false
|
|
64
|
-
end
|
|
65
|
-
private_class_method :port_in_use?
|
|
66
|
-
|
|
67
|
-
def self.kill_process_on_port(port, verbose: false)
|
|
68
|
-
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
69
|
-
# Windows: Use netstat to find PID, then taskkill
|
|
70
|
-
stdout, _stderr, status = Open3.capture3("netstat", "-ano")
|
|
71
|
-
return false unless status.success?
|
|
72
|
-
|
|
73
|
-
pids = []
|
|
74
|
-
stdout.lines.each do |line|
|
|
75
|
-
if line.include?(":#{port}") && line.include?("LISTENING")
|
|
76
|
-
parts = line.split
|
|
77
|
-
pid = parts.last
|
|
78
|
-
pids << pid if pid && pid.match?(/^\d+$/)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
killed_any = false
|
|
83
|
-
pids.uniq.each do |pid|
|
|
84
|
-
begin
|
|
85
|
-
stdout, _stderr, status = Open3.capture3("taskkill", "/F", "/PID", pid)
|
|
86
|
-
if status.success?
|
|
87
|
-
puts "Killed process #{pid} on port #{port}" if verbose
|
|
88
|
-
killed_any = true
|
|
89
|
-
end
|
|
90
|
-
rescue Errno::ENOENT
|
|
91
|
-
# taskkill not found
|
|
92
|
-
return false
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
killed_any
|
|
97
|
-
else
|
|
98
|
-
# Unix/macOS: Use lsof to find PID, then kill
|
|
99
|
-
stdout, _stderr, status = Open3.capture3("lsof", "-ti:#{port}")
|
|
100
|
-
return false unless status.success?
|
|
101
|
-
return true if stdout.strip.empty? # Port is already free
|
|
102
|
-
|
|
103
|
-
pids = stdout.strip.split("\n").map(&:strip).reject(&:empty?)
|
|
104
|
-
return false if pids.empty?
|
|
105
|
-
|
|
106
|
-
killed_any = false
|
|
107
|
-
pids.each do |pid|
|
|
108
|
-
begin
|
|
109
|
-
stdout, _stderr, status = Open3.capture3("kill", "-9", pid)
|
|
110
|
-
if status.success?
|
|
111
|
-
puts "Killed process #{pid} on port #{port}" if verbose
|
|
112
|
-
killed_any = true
|
|
113
|
-
end
|
|
114
|
-
rescue Errno::ENOENT
|
|
115
|
-
# kill not found
|
|
116
|
-
return false
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
killed_any
|
|
121
|
-
end
|
|
122
|
-
rescue Errno::ENOENT
|
|
123
|
-
# Command not found
|
|
124
|
-
false
|
|
12
|
+
warn "[Makit::PortUtility] DEPRECATED: Makit::PortUtility.ensure_port_free is deprecated. " \
|
|
13
|
+
"Use Makit::Port.ensure_port_free(port, verbose: false) instead."
|
|
14
|
+
Makit::Port.ensure_port_free(port, verbose: verbose)
|
|
125
15
|
end
|
|
126
|
-
private_class_method :kill_process_on_port
|
|
127
16
|
end
|
|
128
17
|
end
|
data/lib/makit/version.rb
CHANGED
data/lib/makit.rb
CHANGED