Class: InternalId::ImplicitlyLockingInternalIdGenerator

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

Constant Summary collapse

RecordAlreadyExists =
Class.new(StandardError)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject, scope, usage, init = nil) ⇒ ImplicitlyLockingInternalIdGenerator

Returns a new instance of ImplicitlyLockingInternalIdGenerator.

Raises:

  • (ArgumentError)

101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/models/internal_id.rb', line 101

def initialize(subject, scope, usage, init = nil)
  @subject = subject
  @scope = scope
  @usage = usage
  @init = init

  raise ArgumentError, 'Scope is not well-defined, need at least one column for scope (given: 0)' if scope.empty?

  unless InternalId.usages.has_key?(usage.to_s)
    raise ArgumentError, "Usage '#{usage}' is unknown. Supported values are #{InternalId.usages.keys} from InternalId.usages"
  end
end

Instance Attribute Details

#initObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance or class we're generating an internal id for. scope: Attributes that define the scope for id generation.

Valid keys are `project/project_id` and `namespace/namespace_id`.

usage: Symbol to define the usage of the internal id, see InternalId.usages init: Proc that accepts the subject and the scope and returns Integer|NilClass


97
98
99
# File 'app/models/internal_id.rb', line 97

def init
  @init
end

#scopeObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance or class we're generating an internal id for. scope: Attributes that define the scope for id generation.

Valid keys are `project/project_id` and `namespace/namespace_id`.

usage: Symbol to define the usage of the internal id, see InternalId.usages init: Proc that accepts the subject and the scope and returns Integer|NilClass


97
98
99
# File 'app/models/internal_id.rb', line 97

def scope
  @scope
end

#scope_attrsObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance or class we're generating an internal id for. scope: Attributes that define the scope for id generation.

Valid keys are `project/project_id` and `namespace/namespace_id`.

usage: Symbol to define the usage of the internal id, see InternalId.usages init: Proc that accepts the subject and the scope and returns Integer|NilClass


97
98
99
# File 'app/models/internal_id.rb', line 97

def scope_attrs
  @scope_attrs
end

#subjectObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance or class we're generating an internal id for. scope: Attributes that define the scope for id generation.

Valid keys are `project/project_id` and `namespace/namespace_id`.

usage: Symbol to define the usage of the internal id, see InternalId.usages init: Proc that accepts the subject and the scope and returns Integer|NilClass


97
98
99
# File 'app/models/internal_id.rb', line 97

def subject
  @subject
end

#usageObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance or class we're generating an internal id for. scope: Attributes that define the scope for id generation.

Valid keys are `project/project_id` and `namespace/namespace_id`.

usage: Symbol to define the usage of the internal id, see InternalId.usages init: Proc that accepts the subject and the scope and returns Integer|NilClass


97
98
99
# File 'app/models/internal_id.rb', line 97

def usage
  @usage
end

Instance Method Details

#generateObject

Generates next internal id and returns it init: Block that gets called to initialize InternalId record if not present

Make sure to not throw exceptions in the absence of records (if this is expected).

117
118
119
120
121
122
123
124
125
126
127
# File 'app/models/internal_id.rb', line 117

def generate
  InternalId.internal_id_transactions_increment(operation: :generate, usage: usage)

  next_iid = update_record!(subject, scope, usage, arel_table[:last_value] + 1)

  return next_iid if next_iid

  create_record!(subject, scope, usage, initial_value(subject, scope) + 1)
rescue RecordAlreadyExists
  retry
end

#reset(value) ⇒ Object

Reset tries to rewind to `value-1`. This will only succeed, if `value` stored in database is equal to `last_value`. value: The expected last_value to decrement


132
133
134
135
136
137
138
139
# File 'app/models/internal_id.rb', line 132

def reset(value)
  return false unless value

  InternalId.internal_id_transactions_increment(operation: :reset, usage: usage)

  iid = update_record!(subject, scope.merge(last_value: value), usage, arel_table[:last_value] - 1)
  iid == value - 1
end

#track_greatest(new_value) ⇒ Object

Create a record in internal_ids if one does not yet exist and set its new_value if it is higher than the current last_value


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/models/internal_id.rb', line 143

def track_greatest(new_value)
  InternalId.internal_id_transactions_increment(operation: :track_greatest, usage: usage)

  function = Arel::Nodes::NamedFunction.new('GREATEST', [
    arel_table[:last_value],
    new_value.to_i
  ])

  next_iid = update_record!(subject, scope, usage, function)
  return next_iid if next_iid

  create_record!(subject, scope, usage, [initial_value(subject, scope), new_value].max)
rescue RecordAlreadyExists
  retry
end