Class: Pry::InputLock
Overview
There is one InputLock per input (such as STDIN) as two REPLs on the same input makes things delirious. InputLock serializes accesses to the input so that threads to not conflict with each other. The latest thread to request ownership of the input wins.
Defined Under Namespace
Classes: Interrupt
Class Attribute Summary collapse
-
.global_lock ⇒ Object
Returns the value of attribute global_lock.
-
.input_locks ⇒ Object
Returns the value of attribute input_locks.
Class Method Summary collapse
Instance Method Summary collapse
-
#__with_ownership(&block) ⇒ Object
Adds ourselves to the ownership list.
- #enter_interruptible_region ⇒ Object
-
#initialize ⇒ InputLock
constructor
A new instance of InputLock.
- #interruptible_region(&block) ⇒ Object
- #leave_interruptible_region ⇒ Object
- #with_ownership(&block) ⇒ Object
Constructor Details
#initialize ⇒ InputLock
Returns a new instance of InputLock.
29 30 31 32 33 34 |
# File 'lib/pry/input_lock.rb', line 29 def initialize @mutex = Mutex.new @cond = ConditionVariable.new @owners = [] @interruptible = false end |
Class Attribute Details
.global_lock ⇒ Object
Returns the value of attribute global_lock.
13 14 15 |
# File 'lib/pry/input_lock.rb', line 13 def global_lock @global_lock end |
.input_locks ⇒ Object
Returns the value of attribute input_locks.
12 13 14 |
# File 'lib/pry/input_lock.rb', line 12 def input_locks @input_locks end |
Class Method Details
.for(input) ⇒ Object
19 20 21 22 23 24 25 26 27 |
# File 'lib/pry/input_lock.rb', line 19 def self.for(input) # XXX This method leaks memory, as we never unregister an input once we # are done with it. Fortunately, the leak is tiny (or so we hope). In # usual scenarios, we would leak the StringIO that is passed to be # evaluated from the command line. global_lock.synchronize do input_locks[input] ||= Pry::InputLock.new end end |
Instance Method Details
#__with_ownership(&block) ⇒ Object
Adds ourselves to the ownership list. The last one in the list may access the input through interruptible_region().
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/pry/input_lock.rb', line 38 def __with_ownership(&block) @mutex.synchronize do # Three cases: # 1) There are no owners, in this case we are good to go. # 2) The current owner of the input is not reading the input (it might # just be evaluating some ruby that the user typed). # The current owner will figure out that it cannot go back to reading # the input since we are adding ourselves to the @owners list, which # in turns makes us the current owner. # 3) The owner of the input is in the interruptible region, reading from # the input. It's safe to send an Interrupt exception to interrupt # the owner. It will then proceed like in case 2). # We wait until the owner sets the interruptible flag back # to false, meaning that he's out of the interruptible region. # Note that the owner may receive multiple interrupts since, but that # should be okay (and trying to avoid it is futile anyway). while @interruptible @owners.last.raise Interrupt @cond.wait(@mutex) end @owners << Thread.current end block.call ensure @mutex.synchronize do # We are releasing any desire to have the input ownership by removing # ourselves from the list. @owners.delete(Thread.current) # We need to wake up the thread at the end of the @owners list, but # sadly Ruby doesn't allow us to choose which one we wake up, so we wake # them all up. @cond.broadcast end end |
#enter_interruptible_region ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/pry/input_lock.rb', line 82 def enter_interruptible_region @mutex.synchronize do # We patiently wait until we are the owner. This may happen as another # thread calls with_ownership() because of a binding.pry happening in # another thread. @cond.wait(@mutex) until @owners.last == Thread.current # We are the legitimate owner of the input. We mark ourselves as # interruptible, so other threads can send us an Interrupt exception # while we are blocking from reading the input. @interruptible = true end end |
#interruptible_region(&block) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/pry/input_lock.rb', line 109 def interruptible_region(&block) enter_interruptible_region # XXX Note that there is a chance that we get the interrupt right after # the readline call succeeded, but we'll never know, and we will retry the # call, discarding that piece of input. block.call rescue Interrupt # We were asked to back off. The one requesting the interrupt will be # waiting on the conditional for the interruptible flag to change to false. # Note that there can be some inefficiency, as we could immediately # succeed in enter_interruptible_region(), even before the one requesting # the ownership has the chance to register itself as an owner. # To mitigate the issue, we sleep a little bit. leave_interruptible_region sleep 0.01 retry ensure leave_interruptible_region end |
#leave_interruptible_region ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/pry/input_lock.rb', line 96 def leave_interruptible_region @mutex.synchronize do # We check if we are still the owner, because we could have received an # Interrupt right after the following @cond.broadcast, making us retry. @interruptible = false if @owners.last == Thread.current @cond.broadcast end rescue Interrupt # We need to guard against a spurious interrupt delivered while we are # trying to acquire the lock (the rescue block is no longer in our scope). retry end |
#with_ownership(&block) ⇒ Object
76 77 78 79 80 |
# File 'lib/pry/input_lock.rb', line 76 def with_ownership(&block) # If we are in a nested with_ownership() call (nested pry context), we do nothing. nested = @mutex.synchronize { @owners.include?(Thread.current) } nested ? block.call : __with_ownership(&block) end |