petra_core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +83 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +74 -0
- data/MIT-LICENSE +20 -0
- data/README.md +726 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/examples/continuation_error.rb +125 -0
- data/examples/dining_philosophers.rb +138 -0
- data/examples/showcase.rb +54 -0
- data/lib/petra/components/entries/attribute_change.rb +29 -0
- data/lib/petra/components/entries/attribute_change_veto.rb +37 -0
- data/lib/petra/components/entries/attribute_read.rb +20 -0
- data/lib/petra/components/entries/object_destruction.rb +22 -0
- data/lib/petra/components/entries/object_initialization.rb +19 -0
- data/lib/petra/components/entries/object_persistence.rb +26 -0
- data/lib/petra/components/entries/read_integrity_override.rb +42 -0
- data/lib/petra/components/entry_set.rb +87 -0
- data/lib/petra/components/log_entry.rb +342 -0
- data/lib/petra/components/proxy_cache.rb +209 -0
- data/lib/petra/components/section.rb +543 -0
- data/lib/petra/components/transaction.rb +405 -0
- data/lib/petra/components/transaction_manager.rb +214 -0
- data/lib/petra/configuration/base.rb +132 -0
- data/lib/petra/configuration/class_configurator.rb +309 -0
- data/lib/petra/configuration/configurator.rb +67 -0
- data/lib/petra/core_ext.rb +27 -0
- data/lib/petra/exceptions.rb +181 -0
- data/lib/petra/persistence_adapters/adapter.rb +154 -0
- data/lib/petra/persistence_adapters/file_adapter.rb +239 -0
- data/lib/petra/proxies/abstract_proxy.rb +149 -0
- data/lib/petra/proxies/enumerable_proxy.rb +44 -0
- data/lib/petra/proxies/handlers/attribute_read_handler.rb +45 -0
- data/lib/petra/proxies/handlers/missing_method_handler.rb +47 -0
- data/lib/petra/proxies/method_handlers.rb +213 -0
- data/lib/petra/proxies/module_proxy.rb +12 -0
- data/lib/petra/proxies/object_proxy.rb +310 -0
- data/lib/petra/util/debug.rb +45 -0
- data/lib/petra/util/extended_attribute_accessors.rb +51 -0
- data/lib/petra/util/field_accessors.rb +35 -0
- data/lib/petra/util/registrable.rb +48 -0
- data/lib/petra/util/test_helpers.rb +9 -0
- data/lib/petra/version.rb +5 -0
- data/lib/petra.rb +100 -0
- data/lib/tasks/petra_tasks.rake +5 -0
- data/petra.gemspec +36 -0
- metadata +208 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
|
5
|
+
#----------------------------------------------------------------
|
6
|
+
# Exception Base classes
|
7
|
+
#----------------------------------------------------------------
|
8
|
+
|
9
|
+
# Generic error class wrapping all custom petra exceptions
|
10
|
+
class PetraError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Error class which accepts an options hash and sets its key/value pairs
|
15
|
+
# as instance variables. Inherited classes therefore only have to specify the
|
16
|
+
# corresponding attribute readers
|
17
|
+
#
|
18
|
+
class ExtendedError < PetraError
|
19
|
+
def initialize(**options)
|
20
|
+
options.each do |k, v|
|
21
|
+
instance_variable_set("@#{k}", v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Used whenever a configuration value differs from what petra expects it to be
|
27
|
+
class ConfigurationError < PetraError
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used for generic errors during transaction persistence
|
31
|
+
class PersistenceError < PetraError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Thrown when a transaction should be persisted, but is locked by another instance
|
35
|
+
class TransactionLocked < PetraError
|
36
|
+
end
|
37
|
+
|
38
|
+
class HandlerException < ExtendedError
|
39
|
+
|
40
|
+
def retry!
|
41
|
+
fail Petra::Retry
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Resets the currently active transaction
|
46
|
+
# This will stop the transaction execution, so make sure that you wrap
|
47
|
+
# important code which has to be executed afterwards in an `ensure`
|
48
|
+
#
|
49
|
+
def reset_transaction!
|
50
|
+
@reset = true
|
51
|
+
Petra.transaction_manager.reset_transaction
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Requests a section rollback on the currently active transaction
|
56
|
+
# This will stop the transaction execution, so make sure that you wrap
|
57
|
+
# important code which has to be executed afterwards in an `ensure`
|
58
|
+
#
|
59
|
+
def rollback_transaction!
|
60
|
+
@rollback = true
|
61
|
+
Petra.transaction_manager.rollback_transaction
|
62
|
+
end
|
63
|
+
|
64
|
+
alias rollback! rollback_transaction!
|
65
|
+
alias reset! reset_transaction!
|
66
|
+
|
67
|
+
def continue!
|
68
|
+
fail Petra::ContinuationError, 'The transaction processing cannot be resumed.' unless continuable?
|
69
|
+
@continuation.call
|
70
|
+
end
|
71
|
+
|
72
|
+
def continuable?
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def continuation?
|
79
|
+
!!@continuation
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class ValueComparisonError < HandlerException
|
84
|
+
attr_reader :object # The affected proxy
|
85
|
+
attr_reader :attribute # The affected attribute
|
86
|
+
attr_reader :external_value # The new external attribute value
|
87
|
+
|
88
|
+
#
|
89
|
+
# Tells the current transaction to ignore further errors of this kind
|
90
|
+
# until the attribute value is changed again externally.
|
91
|
+
#
|
92
|
+
# @param [Boolean] update_value
|
93
|
+
# If set to +true+, the read set entry for this attribute is updated with the
|
94
|
+
# new external value. This means that the new value will be visible inside of
|
95
|
+
# the transaction until it changes again.
|
96
|
+
#
|
97
|
+
# Otherwise, the exception is completely ignored and will have no impact
|
98
|
+
# on the values displayed inside the transaction.
|
99
|
+
#
|
100
|
+
def ignore!(update_value: false)
|
101
|
+
Petra.current_transaction.current_section.log_read_integrity_override(object,
|
102
|
+
attribute: attribute,
|
103
|
+
external_value: external_value,
|
104
|
+
update_value: update_value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def continuable?
|
108
|
+
!@reset && !@rollback
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Thrown when a read attribute changed its value externally
|
113
|
+
# If we read AND changed the attribute, a ReadWriteIntegrityError is raised instead
|
114
|
+
class ReadIntegrityError < ValueComparisonError
|
115
|
+
attr_reader :last_read_value # The value we last read for this attribute (before it was changed)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Thrown when an attribute that we previously read AND changed
|
119
|
+
# was also changed externally.
|
120
|
+
class WriteClashError < ValueComparisonError
|
121
|
+
attr_reader :our_value
|
122
|
+
|
123
|
+
#
|
124
|
+
# Tells the transaction to ignore all changes previously done to the current
|
125
|
+
# attribute in the transaction.
|
126
|
+
#
|
127
|
+
def undo_changes!
|
128
|
+
Petra.current_transaction.current_section.log_attribute_change_veto(object,
|
129
|
+
attribute: attribute,
|
130
|
+
external_value: external_value)
|
131
|
+
end
|
132
|
+
|
133
|
+
alias their_value external_value
|
134
|
+
alias use_ours! ignore!
|
135
|
+
alias use_theirs! undo_changes!
|
136
|
+
end
|
137
|
+
|
138
|
+
#----------------------------------------------------------------
|
139
|
+
# Transaction Flow Error Classes
|
140
|
+
#----------------------------------------------------------------
|
141
|
+
|
142
|
+
# Used internally when a lock could not be acquired (non-suspending locking)
|
143
|
+
class LockError < HandlerException
|
144
|
+
attr_reader :lock_type
|
145
|
+
attr_reader :lock_name
|
146
|
+
|
147
|
+
def initialize(lock_type: 'general', lock_name: 'general', processed: false)
|
148
|
+
@lock_type = lock_type
|
149
|
+
@lock_name = lock_name
|
150
|
+
@processed = processed
|
151
|
+
end
|
152
|
+
|
153
|
+
def processed?
|
154
|
+
@processed
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class ControlFlowException < PetraError
|
159
|
+
end
|
160
|
+
|
161
|
+
# This error is thrown only to tell the transaction manager to
|
162
|
+
# abort the current transaction's execution.
|
163
|
+
# This is necessary e.g. after successfully committing a transaction
|
164
|
+
class AbortTransaction < ControlFlowException
|
165
|
+
end
|
166
|
+
|
167
|
+
# An error class which is never passed on out of petra.
|
168
|
+
# It is used to cause a rollback for the currently active petra transaction
|
169
|
+
class Rollback < ControlFlowException
|
170
|
+
end
|
171
|
+
|
172
|
+
# See +Rollback+, this error class is used to trigger a complete
|
173
|
+
# reset on the currently active petra transaction
|
174
|
+
# TODO: Nested transactions anyone?
|
175
|
+
class Reset < ControlFlowException
|
176
|
+
end
|
177
|
+
|
178
|
+
class Retry < ControlFlowException
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/util/registrable'
|
4
|
+
|
5
|
+
module Petra
|
6
|
+
module PersistenceAdapters
|
7
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
8
|
+
|
9
|
+
#
|
10
|
+
# This class acts as an interface and specifies the methods that
|
11
|
+
# a transaction adapter has to implement.
|
12
|
+
#
|
13
|
+
class Adapter
|
14
|
+
include Petra::Util::Registrable
|
15
|
+
acts_as_register :adapter
|
16
|
+
|
17
|
+
class << self
|
18
|
+
alias [] registered_adapter
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# @abstract
|
23
|
+
#
|
24
|
+
# Persists the transaction steps which happened after
|
25
|
+
# the last changes were persisted.
|
26
|
+
#
|
27
|
+
def persist!
|
28
|
+
not_implemented
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Adds the given log entry to the queue to be persisted next.
|
33
|
+
# Fails if the queue already contains the log entry.
|
34
|
+
#
|
35
|
+
# @param [Petra::Components::LogEntry] log_entry
|
36
|
+
#
|
37
|
+
def enqueue(log_entry)
|
38
|
+
if queue.include?(log_entry)
|
39
|
+
fail Petra::PersistenceError, 'A log entry can only be added to a persistence queue once'
|
40
|
+
end
|
41
|
+
queue << log_entry
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# @abstract
|
46
|
+
#
|
47
|
+
# @return [Array<String>] the identifiers of all transactions which are
|
48
|
+
# currently persisted (>= one section finished, but not committed)
|
49
|
+
#
|
50
|
+
def transaction_identifiers
|
51
|
+
not_implemented
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# @abstract
|
56
|
+
#
|
57
|
+
# @param [Petra::Components::Transaction] _transaction
|
58
|
+
#
|
59
|
+
# @return [Array<String>] the names of all savepoints which were previously persisted
|
60
|
+
# for the given transaction
|
61
|
+
#
|
62
|
+
def savepoints(_transaction)
|
63
|
+
not_implemented
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# @abstract
|
68
|
+
#
|
69
|
+
# @param [Petra::Components::Section] _section
|
70
|
+
#
|
71
|
+
# @return [Array<Petra::Components::LogEntry>] All log entries which were previously
|
72
|
+
# persisted for the given section
|
73
|
+
#
|
74
|
+
def log_entries(_section)
|
75
|
+
not_implemented
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Resets the given transaction, meaning that all persisted information is removed
|
80
|
+
#
|
81
|
+
def reset_transaction(_transaction)
|
82
|
+
not_implemented
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @abstract
|
87
|
+
#
|
88
|
+
# Executes the given block after acquiring a global lock
|
89
|
+
#
|
90
|
+
# The actual implementation must ensure that an acquired lock is released in case of
|
91
|
+
# an exception!
|
92
|
+
#
|
93
|
+
# @param [Boolean] suspend
|
94
|
+
# If set to +false+, the method will not suspend if the global lock could not be
|
95
|
+
# acquired. Instead, a Petra::LockError is thrown
|
96
|
+
#
|
97
|
+
# @raise [Petra::LockError] see +suspend+
|
98
|
+
#
|
99
|
+
def with_global_lock(suspend: true)
|
100
|
+
not_implemented
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# @abstract
|
105
|
+
#
|
106
|
+
# Executes the given block after acquiring a transaction based lock,
|
107
|
+
# meaning that other processes which execute something in the same transaction's context
|
108
|
+
# have to wait / abort
|
109
|
+
#
|
110
|
+
# The actual implementation must ensure that an acquired lock is released in case of
|
111
|
+
# an exception!
|
112
|
+
#
|
113
|
+
# @param [Boolean] suspend
|
114
|
+
# If set to +false+, the method will not suspend if the transaction lock could not be
|
115
|
+
# acquired. Instead, a Petra::LockError is thrown
|
116
|
+
#
|
117
|
+
# @raise [Petra::LockError] see +suspend+
|
118
|
+
#
|
119
|
+
def with_transaction_lock(_identifier, suspend: true)
|
120
|
+
not_implemented
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# @abstract
|
125
|
+
#
|
126
|
+
# Executes the given block after acquiring the lock for the given proxy (object)
|
127
|
+
#
|
128
|
+
# The actual implementation must ensure that an acquired lock is released in case of
|
129
|
+
# an exception!
|
130
|
+
#
|
131
|
+
# @param [Petra::Proxies::ObjectProxy] _proxy
|
132
|
+
#
|
133
|
+
# @param [Boolean] suspend
|
134
|
+
# See #with_global_lock
|
135
|
+
#
|
136
|
+
# @raise [Petra::LockError]
|
137
|
+
#
|
138
|
+
def with_object_lock(_proxy, suspend: true)
|
139
|
+
not_implemented
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def queue
|
145
|
+
@queue ||= []
|
146
|
+
end
|
147
|
+
|
148
|
+
def clear_queue!
|
149
|
+
@queue = []
|
150
|
+
end
|
151
|
+
end
|
152
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/persistence_adapters/adapter'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module Petra
|
7
|
+
module PersistenceAdapters
|
8
|
+
class FileAdapter < Adapter
|
9
|
+
|
10
|
+
#----------------------------------------------------------------
|
11
|
+
# Configuration
|
12
|
+
#----------------------------------------------------------------
|
13
|
+
|
14
|
+
# TODO: change this to use e.g. the field accessors
|
15
|
+
class << self
|
16
|
+
def storage_directory
|
17
|
+
::Pathname.new(@storage_directory || '/tmp/petra')
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_writer :storage_directory
|
21
|
+
end
|
22
|
+
|
23
|
+
def persist!
|
24
|
+
return true if queue.empty?
|
25
|
+
|
26
|
+
# rubocop:disable Style/WhileUntilDo
|
27
|
+
# We currently only allow entries for one transaction in the queue
|
28
|
+
with_transaction_lock(queue.first.transaction_identifier) do
|
29
|
+
while (entry = queue.shift) do
|
30
|
+
identifier = create_entry_file(entry)
|
31
|
+
entry.mark_as_persisted!(identifier)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# rubocop:enable Style/WhileUntilDo
|
35
|
+
end
|
36
|
+
|
37
|
+
def transaction_identifiers
|
38
|
+
# Wait until no other transaction is doing stuff that might lead to inconsistent data
|
39
|
+
with_global_lock do
|
40
|
+
ensure_directory_existence('transactions')
|
41
|
+
storage_file_name('transactions').children.select(&:directory?).map(&:basename).map(&:to_s)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def savepoints(transaction)
|
46
|
+
with_transaction_lock(transaction.identifier) do
|
47
|
+
return [] unless File.exist? storage_file_name('transactions', transaction.identifier)
|
48
|
+
storage_file_name('transactions', transaction.identifier).children.select(&:directory?).map do |f|
|
49
|
+
::YAML.load_file(f.join('information.yml').to_s)[:savepoint]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_entries(section)
|
55
|
+
with_transaction_lock(section.transaction.identifier) do
|
56
|
+
section_dir = storage_file_name(*section_dirname(section))
|
57
|
+
|
58
|
+
# If the given section has never been persisted before, we don't have to
|
59
|
+
# search further for log entries
|
60
|
+
return [] unless section_dir.exist?
|
61
|
+
|
62
|
+
section_dir.children.select { |f| f.extname == '.entry' }.map do |f|
|
63
|
+
entry_hash = ::YAML.load_file(f.to_s)
|
64
|
+
Petra::Components::LogEntry.from_hash(section, entry_hash)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Removes everything that was persisted while executing the given transaction
|
71
|
+
#
|
72
|
+
def reset_transaction(transaction)
|
73
|
+
with_transaction_lock(transaction) do
|
74
|
+
if storage_file_name('transactions', transaction.identifier).exist?
|
75
|
+
FileUtils.rm_r(storage_file_name('transactions', transaction.identifier))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def with_global_lock(**options, &block)
|
81
|
+
with_file_lock('global.persistence', **options, &block)
|
82
|
+
rescue Petra::LockError => e
|
83
|
+
raise e if e.processed?
|
84
|
+
exception = Petra::LockError.new(lock_type: 'global', lock_name: 'global.persistence', processed: true)
|
85
|
+
raise exception, 'The global lock could not be acquired.'
|
86
|
+
end
|
87
|
+
|
88
|
+
def with_transaction_lock(transaction, **options, &block)
|
89
|
+
identifier = transaction.is_a?(Petra::Components::Transaction) ? transaction.identifier : transaction
|
90
|
+
with_file_lock("transaction.#{identifier}", **options, &block)
|
91
|
+
rescue Petra::LockError => e
|
92
|
+
raise e if e.processed?
|
93
|
+
exception = Petra::LockError.new(lock_type: 'transaction', lock_name: identifier, processed: true)
|
94
|
+
raise exception, "The transaction lock '#{identifier}' could not be acquired."
|
95
|
+
end
|
96
|
+
|
97
|
+
def with_object_lock(proxy, **options, &block)
|
98
|
+
key = proxy.__object_key.gsub(/[^a-zA-Z0-9]/, '-')
|
99
|
+
with_file_lock("proxy.#{key}", **options, &block)
|
100
|
+
rescue Petra::LockError => e
|
101
|
+
raise e if e.processed?
|
102
|
+
exception = Petra::LockError.new(lock_type: 'object', lock_name: proxy.__object_key, processed: true)
|
103
|
+
raise exception, "The object lock '#{proxy.__object_id}' could not be acquired."
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
#
|
109
|
+
# The Ruby version of `mkdir -p`
|
110
|
+
#
|
111
|
+
# @param [*Array] path
|
112
|
+
# The path to the directory in a format #storage_file_name understands
|
113
|
+
#
|
114
|
+
def ensure_directory_existence(*path)
|
115
|
+
FileUtils.mkdir_p(storage_file_name(*path))
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Executes the given block after acquiring a lock on the given filename
|
120
|
+
# If the lock is already held by this process, but not with the same file handle,
|
121
|
+
# the function will not try to acquire it again.
|
122
|
+
#
|
123
|
+
# @param [String] filename
|
124
|
+
#
|
125
|
+
# @param [Boolean] suspend
|
126
|
+
# If set to +false+, a LockError is thrown if the lock could not be acquired.
|
127
|
+
#
|
128
|
+
# @raise [Petra::LockError] If +suspend+ is set to +false+ and the lock could not be acquired
|
129
|
+
#
|
130
|
+
# TODO: sanitize file names
|
131
|
+
#
|
132
|
+
def with_file_lock(filename, suspend: true)
|
133
|
+
Thread.current[:__petra_file_locks] ||= []
|
134
|
+
lock_held = Thread.current[:__petra_file_locks].include?(lock_file_name(filename).to_s)
|
135
|
+
|
136
|
+
return yield if lock_held
|
137
|
+
|
138
|
+
File.open(lock_file_name(filename), File::RDWR | File::CREAT, 0o644) do |f|
|
139
|
+
if suspend
|
140
|
+
f.flock(File::LOCK_EX)
|
141
|
+
else
|
142
|
+
unless f.flock(File::LOCK_EX | File::LOCK_NB)
|
143
|
+
Petra.logger.debug "#{Thread.current.name}: Could not acquire '#{filename}'", :red
|
144
|
+
fail Petra::LockError
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
Petra.logger.debug "#{Thread.current.name}: Acquired Lock: #{filename}", :purple
|
149
|
+
|
150
|
+
Thread.current[:__petra_file_locks] << lock_file_name(filename).to_s
|
151
|
+
|
152
|
+
begin
|
153
|
+
yield
|
154
|
+
ensure
|
155
|
+
# Should be done automatically when the file handle is closed, but who knows
|
156
|
+
f.flock(File::LOCK_UN)
|
157
|
+
Petra.logger.debug "#{Thread.current.name}: Released Lock: #{filename}", :cyan
|
158
|
+
Thread.current[:__petra_file_locks].delete(lock_file_name(filename).to_s)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Builds the path to a given file based on the configured storage directory
|
165
|
+
#
|
166
|
+
# @example STORAGE_DIR/oompa/loompa
|
167
|
+
# storage_file_name('oompa', 'loompa')
|
168
|
+
#
|
169
|
+
def storage_file_name(*parts)
|
170
|
+
self.class.storage_directory.join(*parts)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# @param [String] filename
|
175
|
+
#
|
176
|
+
# @return [String] the path to a lockfile with the given name
|
177
|
+
#
|
178
|
+
def lock_file_name(filename)
|
179
|
+
# Make sure the locks directory actually exists
|
180
|
+
ensure_directory_existence('locks')
|
181
|
+
storage_file_name('locks', "petra.#{filename}.lock")
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Opens a file within the storage directory and yields its handle
|
186
|
+
#
|
187
|
+
def with_storage_file(*parts, mode: 'r', perm: 0o644, &block)
|
188
|
+
File.open(storage_file_name(*parts), mode, perm, &block)
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Creates a directory for the given section.
|
193
|
+
# This includes an `information.yml` file within the directory
|
194
|
+
# which contains information about the current savepoint and transaction
|
195
|
+
#
|
196
|
+
# @param [Petra::Components::Section] section
|
197
|
+
#
|
198
|
+
def add_section_directory(section)
|
199
|
+
dir = section_dirname(section)
|
200
|
+
ensure_directory_existence(*dir)
|
201
|
+
|
202
|
+
# If there is already a section directory/information file, we are done.
|
203
|
+
return if storage_file_name(*dir, 'information.yml').exist?
|
204
|
+
|
205
|
+
section_hash = {transaction_identifier: section.transaction.identifier,
|
206
|
+
savepoint: section.savepoint,
|
207
|
+
savepoint_version: section.savepoint_version}
|
208
|
+
with_storage_file(*dir, 'information.yml', mode: 'w') do |f|
|
209
|
+
::YAML.dump(section_hash, f)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Creates a file for the given LogEntry.
|
215
|
+
# It contains the necessary information to deserialize it later.
|
216
|
+
#
|
217
|
+
# These files are placed within a section directory (/transaction/section/entry)
|
218
|
+
#
|
219
|
+
# @param [Petra::Components::LogEntry] entry
|
220
|
+
#
|
221
|
+
def create_entry_file(entry)
|
222
|
+
add_section_directory(entry.section)
|
223
|
+
t = Time.now
|
224
|
+
filename = "#{t.to_i}.#{t.nsec}.entry"
|
225
|
+
with_storage_file(*section_dirname(entry.section), filename, mode: 'w') do |f|
|
226
|
+
::YAML.dump(entry.to_h(entry_identifier: filename), f)
|
227
|
+
end
|
228
|
+
filename
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# @return [Array<String>] The directory name components for the given section within STORAGE_DIR
|
233
|
+
#
|
234
|
+
def section_dirname(section)
|
235
|
+
['transactions', section.transaction.identifier, section.savepoint_version.to_s]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|