Class: BarkestCore::GlobalStatus

Inherits:
Object
  • Object
show all
Defined in:
app/models/barkest_core/global_status.rb

Overview

An interface to a global status/lock file.

The global status/lock file is a simple two line file. The first line is the global status message. The second line is the global status progress.

The real magic comes when we take advantage of exclusive locks. The process that will be managing the status takes an exclusive lock on the status/lock file. This prevents any other process from taking an exclusive lock. It does not prevent other processes from reading from the file.

So the main process can update the file at any time, until it releases the lock. The other processes can read the file at any time, and test for the lock state to determine if the main process is still busy.

Constant Summary collapse

FailureToLock =

The exception raised in the lock_for method when raise_on_failure is set.

Class.new(StandardError)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(status_file = nil) ⇒ GlobalStatus

Creates a new GlobalStatus object.

If you specify a status file, then that file will be used for the locking and status reporting. Otherwise, the “global_lock” file will be used.



30
31
32
33
34
# File 'app/models/barkest_core/global_status.rb', line 30

def initialize(status_file = nil)
  @lock_handle = nil
  @stat_handle = nil
  @status_file_path = status_file
end

Class Method Details

.currentObject

Gets the current status from the status/lock file.

See #get_status for a description of the returned hash.



228
229
230
# File 'app/models/barkest_core/global_status.rb', line 228

def self.current
  global_instance.get_status
end

.lock_for(raise_on_failure = false, &block) ⇒ Object

Runs the provided block with a lock on the status/lock file.

If a lock can be acquired, a GlobalStatus object is yielded to the block. The lock will automatically be released when the block exits.

If a lock cannot be acquire, then false is yielded to the block. The block needs to test for this case to ensure that the appropriate error handling is performed.

The only time that the block is not called is if raise_on_failure is set, in which case an exception is raised instead of yielding to the block.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'app/models/barkest_core/global_status.rb', line 245

def self.lock_for(raise_on_failure = false, &block)
  return unless block_given?
  status = GlobalStatus.new
  if status.acquire_lock
    begin
      yield status
    ensure
      status.release_lock
    end
  else
    raise BarkestCore::GlobalStatus::FailureToLock.new if raise_on_failure
    yield false
  end
end

.locked?Boolean

Determines if any process currently holds the lock on the status/lock file.

Returns true if the file is locked, otherwise returns false.

Returns:

  • (Boolean)


219
220
221
# File 'app/models/barkest_core/global_status.rb', line 219

def self.locked?
  global_instance.is_locked?
end

Instance Method Details

#acquire_lockObject

Acquires the lock on the status/lock file.

Returns true on success or if this instance already holds the lock. Returns false if another process holds the lock.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'app/models/barkest_core/global_status.rb', line 189

def acquire_lock
  return true if @lock_handle
  begin
    @lock_handle = File.open(lock_file_path, File::RDWR | File::CREAT)
    raise StandardError.new('Already locked') unless @lock_handle.flock(File::LOCK_EX | File::LOCK_NB)
    @lock_handle.rewind
    @lock_handle.truncate 0
    @stat_handle = File.open(status_file_path, File::RDWR | File::CREAT)
    raise StandardError.new('Failed to open status') unless @stat_handle
    @stat_handle.rewind
    @stat_handle.truncate 0
  rescue
    if @stat_handle
      @stat_handle.close rescue nil
    end
    if @lock_handle
      @lock_handle.flock(File::LOCK_UN) rescue nil
      @lock_handle.close rescue nil
    end
    @stat_handle = nil
    @lock_handle = nil
  end
  !!@lock_handle
end

#get_messageObject

Gets the current status message from the status/lock file.



68
69
70
# File 'app/models/barkest_core/global_status.rb', line 68

def get_message
  get_status[:message]
end

#get_percentageObject

Gets the current progress from the status/lock file.



74
75
76
77
# File 'app/models/barkest_core/global_status.rb', line 74

def get_percentage
  r = get_status[:percent]
  r.blank? ? nil : r.to_i
end

#get_statusObject

Gets the current status from the status/lock file.

Returns a hash with three elements:

message

The current status message.

percent

The current status progress.

locked

The current lock state of the status/lock file. (true for locked, false for unlocked)



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'app/models/barkest_core/global_status.rb', line 93

def get_status
  r = {}
  if have_lock?
    @stat_handle.rewind
    r[:message] = (@stat_handle.eof? ? 'The current process is busy.' : @stat_handle.readline.strip)
    r[:percent] = (@stat_handle.eof? ? '' : @stat_handle.readline.strip)
    r[:locked] = true
  elsif is_locked?
    if File.exist?(status_file_path)
      begin
        File.open(status_file_path, 'r') do |f|
          r[:message] = (f.eof? ? 'The system is busy.' : f.readline.strip)
          r[:percent] = (f.eof? ? '' : f.readline.strip)
        end
      rescue
        r[:message] = 'The system appears busy.'
        r[:percent] = ''
      end
    else
      r[:message] = 'No status file.'
      r[:percent] = ''
    end
    r[:locked] = true
  else
    r[:message] = 'The system is no longer busy.'
    r[:percent] = '-'
    r[:locked] = false
  end
  r
end

#have_lock?Boolean

Determines if this instance has a lock on the status/lock file.

Returns:

  • (Boolean)


50
51
52
# File 'app/models/barkest_core/global_status.rb', line 50

def have_lock?
  !!@lock_handle
end

#is_locked?Boolean

Determines if any process has a lock on the status/lock file.

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
# File 'app/models/barkest_core/global_status.rb', line 56

def is_locked?
  begin
    return true if have_lock?
    return true unless acquire_lock
  ensure
    release_lock
  end
  false
end

#lock_file_pathObject

Gets the path to the global lock file.



44
45
46
# File 'app/models/barkest_core/global_status.rb', line 44

def lock_file_path
  @lock_file_path ||= WorkPath.path_for('global_lock')
end

#release_lockObject

Releases the lock on the status/lock file if this instance holds the lock.

Returns true.



169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/models/barkest_core/global_status.rb', line 169

def release_lock
  return true unless @lock_handle
  begin
    set_message ''
    @lock_handle.flock(File::LOCK_UN)
  ensure
    @stat_handle.close rescue nil
    @lock_handle.close rescue nil
    @stat_handle = @lock_handle = nil
  end

  true
end

#set_message(value) ⇒ Object

Sets the status message if this instance has a lock on the status/lock file.

Returns true after successfully setting the message. Returns false if this instance does not currently hold the lock.



130
131
132
133
134
# File 'app/models/barkest_core/global_status.rb', line 130

def set_message(value)
  return false unless have_lock?
  cur = get_status
  set_status(value, cur[:percent])
end

#set_percentage(value) ⇒ Object

Sets the status progress if this instance has a lock on the status/lock file.

Returns true after successfully setting the progress. Returns false if this instance does not currently hold the lock.



142
143
144
145
146
# File 'app/models/barkest_core/global_status.rb', line 142

def set_percentage(value)
  return false unless have_lock?
  cur = get_status
  set_status(cur[:message], value)
end

#set_status(message, percentage) ⇒ Object

Sets the status message and progress if this instance has a lock on the status/lock file.

Returns true after successfully setting the status. Returns false if this instance does not currently hold the lock.



154
155
156
157
158
159
160
161
162
# File 'app/models/barkest_core/global_status.rb', line 154

def set_status(message, percentage)
  return false unless have_lock?
  @stat_handle.rewind
  @stat_handle.truncate 0
  @stat_handle.write(message.to_s.strip + "\n")
  @stat_handle.write(percentage.to_s.strip + "\n")
  @stat_handle.flush
  true
end

#status_file_pathObject

Gets the path to the global status file.



38
39
40
# File 'app/models/barkest_core/global_status.rb', line 38

def status_file_path
  @status_file_path ||= WorkPath.path_for('global_status')
end