Class: Yobi::Cancellation

Inherits:
Object
  • Object
show all
Defined in:
lib/yobi/cancellation.rb,
sig/yobi.rbs

Overview

A cancellation token for a long-running Restic run.

Pass one to Yobi::Repository#with_cancellation, then call #cancel! from another thread to stop whichever long-running method (#backup, #restore, #check, #prune, #forget, #copy) is running on the repository it returns:

token = Yobi::Cancellation.new
Thread.new { token.cancel! if user_clicked_cancel }
repo.with_cancellation(token).backup(source: "/data")
# => raises Yobi::Cancelled

#cancel! sends the Restic process SIGINT rather than SIGKILL, so Restic removes its own repository lock on the way out and no restic unlock is needed afterwards. An interrupted backup writes no snapshot; data it had already uploaded is generally left unreferenced until the next prune rather than being reused, so cancelling does discard in-progress work.

Safe to call #cancel! before the run starts (the run then raises without spawning Restic at all), after it has finished (a no-op), and from any thread. A token tracks one run at a time and is not reusable once cancelled.

Constant Summary collapse

DEFAULT_SIGNAL =

The signal sent to Restic by #cancel! unless overridden.

Returns:

"INT"

Instance Method Summary collapse

Constructor Details

#initializeCancellation

Returns a new instance of Cancellation.



30
31
32
33
34
35
# File 'lib/yobi/cancellation.rb', line 30

def initialize
  @mutex = Mutex.new
  @cancelled = false
  @signal = DEFAULT_SIGNAL
  @pid = nil
end

Instance Method Details

#attach(pid) ⇒ void

This method returns an undefined value.

Attaches a freshly spawned Restic process. If #cancel! already landed before the spawn, the process is signalled immediately. Called by Yobi::Restic; not part of the public API.

Parameters:



64
65
66
67
68
69
70
# File 'lib/yobi/cancellation.rb', line 64

def attach(pid) # :nodoc:
  cancelled, signal = @mutex.synchronize do
    @pid = pid
    [@cancelled, @signal]
  end
  signal_pid(pid, signal) if cancelled
end

#cancel!(signal: DEFAULT_SIGNAL) ⇒ Boolean

Requests cancellation, signalling the attached Restic process if one is currently running. Returns true the first time, false if this token was already cancelled. Idempotent and thread-safe.

signal: overrides the signal sent; the SIGINT default is what lets Restic clean up its lock, so override it only when you have a reason to.

Parameters:

  • (defaults to: DEFAULT_SIGNAL)

Returns:



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/yobi/cancellation.rb', line 48

def cancel!(signal: DEFAULT_SIGNAL)
  pid = nil
  @mutex.synchronize do
    return false if @cancelled

    @cancelled = true
    @signal = signal
    pid = @pid
  end
  signal_pid(pid, signal)
  true
end

#cancelled?Boolean

True once #cancel! has been called.

Returns:



38
39
40
# File 'lib/yobi/cancellation.rb', line 38

def cancelled?
  @mutex.synchronize { @cancelled }
end

#detachvoid

This method returns an undefined value.

Detaches the current process once it has been reaped, so a later #cancel! can't signal a recycled PID. Called by Yobi::Restic; not part of the public API.



75
76
77
# File 'lib/yobi/cancellation.rb', line 75

def detach # :nodoc:
  @mutex.synchronize { @pid = nil }
end

#inspectString

Returns:



79
80
81
# File 'lib/yobi/cancellation.rb', line 79

def inspect
  "#<#{self.class} cancelled=#{cancelled?}>"
end