Module: WifiWand::ProcessProbeManager

Included in:
CaptivePortalChecker, NetworkConnectivityTester
Defined in:
lib/wifi_wand/services/process_probe_manager.rb

Instance Method Summary collapse

Instance Method Details

#finalize_probe(probe, grace: helper_result_grace) ⇒ Object

Finalizes a successful probe that has produced a result. Unlike terminate_probe, this first waits for the helper to exit on its own before attempting termination. This is the standard path for helpers that have finished their work and are expected to exit promptly.



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 40

def finalize_probe(probe, grace: helper_result_grace)
  pid = probe[:pid]
  probe[:reader]&.close unless probe[:reader]&.closed?
  return unless pid

  # Wait briefly for helper exit before clearing the PID.
  if probe_reaped_within_grace?(pid, grace: grace)
    probe[:pid] = nil
  else
    # Helper produced a result but is still running; escalate cleanup.
    terminate_probe(probe, grace: grace)
  end
end

#helper_exit_poll_intervalObject



12
13
14
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 12

def helper_exit_poll_interval
  0.01
end

#helper_result_graceObject

Raises:

  • (NotImplementedError)


5
6
7
8
9
10
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 5

def helper_result_grace
  return self.class::HELPER_RESULT_GRACE if self.class.const_defined?(:HELPER_RESULT_GRACE, false)

  raise NotImplementedError,
    "#{self.class} must define HELPER_RESULT_GRACE to include ProcessProbeManager"
end

#probe_reaped_within_grace?(pid, grace:) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 72

def probe_reaped_within_grace?(pid, grace:)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + grace

  loop do
    return true if reap_probe(pid)

    remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
    break if remaining <= 0

    sleep([helper_exit_poll_interval, remaining].min)
  end

  false
end

#reap_probe(pid) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 54

def reap_probe(pid)
  return unless pid

  Process.wait(pid, Process::WNOHANG) || nil
rescue Errno::ECHILD
  nil
end

#terminate_probe(probe, grace: helper_result_grace) ⇒ Object

Forces termination of a probe process. Sends SIGTERM, waits for grace, then escalates to SIGKILL if necessary. Always ensures the reader pipe is closed and the PID is cleared from the probe hash.



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 23

def terminate_probe(probe, grace: helper_result_grace)
  pid = probe[:pid]
  probe[:reader]&.close unless probe[:reader]&.closed?
  return unless pid

  Process.kill('TERM', pid)
  wait_for_probe_exit(pid, grace: grace)
  probe[:pid] = nil
rescue Errno::ESRCH, Errno::ECHILD
  probe[:pid] = nil
  nil
end

#terminate_probes(probes, grace: helper_result_grace) ⇒ Object



16
17
18
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 16

def terminate_probes(probes, grace: helper_result_grace)
  probes.each { |probe| terminate_probe(probe, grace: grace) }
end

#wait_for_killed_probe_reap(pid) ⇒ Object



87
88
89
90
91
92
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 87

def wait_for_killed_probe_reap(pid)
  Process.wait(pid)
  true
rescue Errno::ECHILD
  true
end

#wait_for_probe_exit(pid, grace: helper_result_grace) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/wifi_wand/services/process_probe_manager.rb', line 62

def wait_for_probe_exit(pid, grace: helper_result_grace)
  return true if probe_reaped_within_grace?(pid, grace: grace)

  # Escalation: SIGKILL after grace period expired
  Process.kill('KILL', pid)
  wait_for_killed_probe_reap(pid)
rescue Errno::ESRCH, Errno::ECHILD
  true
end