Class: Amp::Journal

Inherits:
Object show all
Defined in:
lib/amp/repository/journal.rb

Overview

Provides a journal interface so when a large number of transactions are occurring, and any one could fail, we can rollback the changes.

Constant Summary collapse

DEFAULT_OPTS =
{:reporter => StandardErrorReporter, :after_close => nil}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(reporter = StandardErrorReporter, journal = "journal#{rand(10000)}", createmode = nil, &after_close) ⇒ Journal

Initializes the journal to get ready for some transactions.

Parameters:

  • reporter (#report) (defaults to: StandardErrorReporter)

    an object that will keep track of any alerts we have to send out. Must respond to #report.

  • journal (String) (defaults to: "journal#{rand(10000)}")

    the path to the journal file to use

  • createmode (Integer) (defaults to: nil)

    An octal number that sets the filemode of the journal file we’ll be using

  • after_close (Proc)

    A proc to call (with no args) after we close (finish) the transaction.



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/amp/repository/journal.rb', line 42

def initialize(reporter=StandardErrorReporter, journal="journal#{rand(10000)}", createmode=nil, &after_close)      
  @count = 1
  @reporter = reporter
  @after_close = after_close
  @entries = []
  @map = {}
  @journal_file = journal
  
  @file = open(@journal_file, "w")
  
  FileUtils.chmod(createmode & 0666, @journal_file) unless createmode.nil?
end

Instance Attribute Details

#after_closeObject

Returns the value of attribute after_close.



14
15
16
# File 'lib/amp/repository/journal.rb', line 14

def after_close
  @after_close
end

#journalObject

Returns the value of attribute journal.



14
15
16
# File 'lib/amp/repository/journal.rb', line 14

def journal
  @journal
end

#reportObject

Returns the value of attribute report.



14
15
16
# File 'lib/amp/repository/journal.rb', line 14

def report
  @report
end

Class Method Details

.rollback(file) ⇒ Object

If we crashed during an abort, the journal file is gonna be sitting aorund somewhere. So, we should rollback any changes it left lying around.

Parameters:

  • file (String)

    the journal file to use during the rollback



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/amp/repository/journal.rb', line 179

def self.rollback(file)
  files = {}
  fp = open(file)
  fp.each_line do |line|
    file, offset = line.split("\0")
    files[file] = offset.to_i
  end
  fp.close
  files.each do |file, offset|
    if o > 0
      fp = open(file, "a")
      fp.truncate o.to_i
      fp.close
    else
      fp = open(f)
      fn = fp.path
      fp.close
      FileUtils.safe_unlink fn
    end
  end
  FileUtils.safe_unlink file
end

.start(file, opts = DEFAULT_OPTS) ⇒ Amp::Journal

Returns:



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/amp/repository/journal.rb', line 18

def self.start(file, opts=DEFAULT_OPTS)
  journal = Journal.new opts[:reporter], file, &opts[:after_close]
  
  if block_given?
    begin
      yield journal
    ensure
      journal.close
    end
  end
  
  journal
end

Instance Method Details

#abortObject

Abort, abort! abandon ship! This rolls back any changes we’ve made during the current journalling session.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/amp/repository/journal.rb', line 157

def abort
  return unless @entries && @entries.any?
  @reporter.report "transaction abort!\n"
  @entries.each do |hash|
    file, offset = hash[:file], hash[:offset]
    begin
      fp = open(File.join(".hg","store",file), "a")
      fp.truncate offset
      fp.close
    rescue
      @reporter.report "Failed to truncate #{File.join(".hg","store",file)}\n"
    end
  end
  @entries = []
  @reporter.report "rollback completed\n"
end

#add_entry(array) ⇒ Object Also known as: <<

Adds an entry to the journal. Since all our files are just being appended to all the time, all we really need is to keep track of how long the file was when we last knew it to be safe. In other words, if the file started off at 20 bytes, then an error happened, we just truncate it to 20 bytes.

All params should be contained in the array

Parameters:

  • file

    the name of the file we’re modifying and need to track

  • offset

    the length of the file we’re storing

  • data

    any extra data to hold onto



77
78
79
80
81
82
83
84
85
86
# File 'lib/amp/repository/journal.rb', line 77

def add_entry(array)
  file, offset, data = array[0], array[1], array[2]
  return if @map[file]
  @entries << {:file => file, :offset => offset, :data => data}
  @map[file] = @entries.size - 1
  
  # tell the journal how to truncate this revision
  @file.write("#{file}\0#{offset}\n")
  @file.flush
end

#closeObject

Closes up the journal. Will call the after_close proc passed during instantiation.



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/amp/repository/journal.rb', line 141

def close
  @count -= 1
  return if @count != 0
  @file.close
  @entries = []
  if @after_close
    @after_close.call
  else
    FileUtils.safe_unlink(@journal_file)
  end
  @journal_file = nil
end

#deleteObject

Kills the journal - used when shit goes down and we gotta give up on the transactions.



58
59
60
61
62
63
64
# File 'lib/amp/repository/journal.rb', line 58

def delete
  if @journal_file
    abort if @entries.any?
    @file.close
    FileUtils.safe_unlink @journal_file
  end
end

#find_file(file) ⇒ Hash Also known as: find

Finds the entry for a given file’s path

Parameters:

  • file (String)

    the path to the file

Returns:

  • (Hash)

    A hash with the values :file, :offset, and :data, as they were when they were stored by #add_entry or #update



97
98
99
100
# File 'lib/amp/repository/journal.rb', line 97

def find_file(file)
  return @entries[@map[file]] if @map[file]
  nil
end

#nestObject

No godly idea what this is for



127
128
129
130
# File 'lib/amp/repository/journal.rb', line 127

def nest
  @count += 1
  self
end

#replace(file, offset, data = nil) ⇒ Object Also known as: update

Updates an entry’s data, based on the filename. The file must already have been journaled.

Parameters:

  • file (String)

    the file to update

  • offset (Fixnum)

    the new offset to store

  • data (String) (defaults to: nil)

    the new data to store

Raises:

  • (IndexError)


113
114
115
116
117
118
119
# File 'lib/amp/repository/journal.rb', line 113

def replace(file, offset, data=nil)
  raise IndexError.new("journal lookup failed #{file}") unless @map[file]
  index = @map[file]
  @entries[index] = {:file => file, :offset => offset, :data => data}
  @file.write("#{file}\0#{offset}\n")
  @file.flush
end

#running?Boolean

Is the journal running right now?

Returns:



134
135
136
# File 'lib/amp/repository/journal.rb', line 134

def running?
  @count > 0
end