pampa_dispatcher 1.1.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/pampa_dispatcher.rb +219 -0
  3. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 854adb6af15723ba6c9a2f4ac5af8acc8a47f4ba
4
+ data.tar.gz: 11091e316eab1a12bfb30114269ea612693aa59a
5
+ SHA512:
6
+ metadata.gz: a89cd5510431a901f9677bd35cceabcf20d281e0d514f1063cc83c514335a6998ae14109ea15078e455d61853ac63358bbc0ee86a7a637b5fe4faff5bc81ab82
7
+ data.tar.gz: 324445697c97efa5ed2089e43326a9363165a7d7db044929d07eacf9834cbf3c2526bfb56d7b77e1475196f058c350d92aa6cabab8ac895c3250b23b8e7a3bbc
@@ -0,0 +1,219 @@
1
+ require 'pampa_workers'
2
+
3
+ module BlackStack
4
+
5
+ class Dispatcher
6
+ attr_accessor :name
7
+ # database information
8
+ # :field_times, :field_start_time and :field_end_time maybe nil
9
+ attr_accessor :table
10
+ attr_accessor :field_primary_key
11
+ attr_accessor :field_id
12
+ attr_accessor :field_time
13
+ attr_accessor :field_times
14
+ attr_accessor :field_start_time
15
+ attr_accessor :field_end_time
16
+ # max number of records assigned to a worker that have not started (:start_time field is nil)
17
+ attr_accessor :queue_size
18
+ # max number of minutes that a job should take to process. if :end_time keep nil x minutes
19
+ # after :start_time, that's considered as the job has failed or interrumped
20
+ attr_accessor :max_job_duration_minutes
21
+ # max number of times that a record can start to process & fail (:start_time field is not nil,
22
+ # but :end_time field is still nil after :max_job_duration_minutes)
23
+ attr_accessor :max_try_times
24
+ # additional function to decide how many records are pending for processing
25
+ # it should returns an integer
26
+ # keep it nil if you want to run the default function
27
+ attr_accessor :ocuppied_function
28
+ # additional function to decide if the worker can dispatch or not
29
+ # example: use this function when you want to decide based on the remaining credits of the client
30
+ # it should returns true or false
31
+ # keep it nil if you want it returns always true
32
+ attr_accessor :allowing_function
33
+ # additional function to choose the records to launch
34
+ # it should returns an array of IDs
35
+ # keep this parameter nil if you want to use the default algorithm
36
+ attr_accessor :selecting_function
37
+ # additional function to choose the records to retry
38
+ # keep this parameter nil if you want to use the default algorithm
39
+ attr_accessor :relaunching_function
40
+ # additional function to perform the update on a record to retry
41
+ # keep this parameter nil if you want to use the default algorithm
42
+ attr_accessor :relauncher_function
43
+ # additional function to perform the update on a record to flag the starting of the job
44
+ # by default this function will set the :field_start_time field with the current datetime, and it will increase the :field_times counter
45
+ # keep this parameter nil if you want to use the default algorithm
46
+ attr_accessor :starter_function
47
+ # additional function to perform the update on a record to flag the finishing of the job
48
+ # by default this function will set the :field_end_time field with the current datetime
49
+ # keep this parameter nil if you want to use the default algorithm
50
+ attr_accessor :finisher_function
51
+
52
+
53
+ # setup dispatcher configuration here
54
+ def initialize(h)
55
+ self.name = h[:name]
56
+ self.table = h[:table]
57
+ self.field_primary_key = h[:field_primary_key]
58
+ self.field_id = h[:field_id]
59
+ self.field_time = h[:field_time]
60
+ self.field_times = h[:field_times]
61
+ self.field_start_time = h[:field_start_time]
62
+ self.field_end_time = h[:field_end_time]
63
+ self.queue_size = h[:queue_size]
64
+ self.max_job_duration_minutes = h[:max_job_duration_minutes]
65
+ self.max_try_times = h[:max_try_times]
66
+ self.ocuppied_function = h[:ocuppied_function]
67
+ self.allowing_function = h[:allowing_function]
68
+ self.selecting_function = h[:selecting_function]
69
+ self.relaunching_function = h[:relaunching_function]
70
+ self.relauncher_function = h[:relauncher_function]
71
+ end
72
+
73
+ # decide how many records are pending for processing
74
+ # it will count the number if records with :reservation_id == worker.id, and :start_time == nil
75
+ # it returns an integer
76
+ def ocuppied_slots(worker)
77
+ if self.ocuppied_function.nil?
78
+ return self.table.where(self.field_id.to_sym => worker.id, self.field_start_time.to_sym => nil).count if !self.field_start_time.nil?
79
+ return self.table.where(self.field_id.to_sym => worker.id).count if self.field_start_time.nil?
80
+ else
81
+ # TODO: validar que retorna un entero
82
+ return self.ocuppied_function.call(worker, self)
83
+ end
84
+ end
85
+
86
+ # returns the number of free slots in the procesing queue of this worker
87
+ def available_slots(worker)
88
+ ocuppied = self.ocuppied_slots(worker)
89
+ allowed = self.queue_size
90
+ if ocuppied > allowed
91
+ return 0
92
+ else
93
+ return allowed - ocuppied
94
+ end
95
+ end
96
+
97
+ # decide if the worker can dispatch or not
98
+ # example: use this function when you want to decide based on the remaining credits of the client
99
+ # returns always true
100
+ def allowing(worker)
101
+ if self.allowing_function.nil?
102
+ return true
103
+ else
104
+ # TODO: validar que retorna true o false
105
+ return self.allowing_function.call(worker, self)
106
+ end
107
+ end
108
+
109
+ # choose the records to dispatch
110
+ # returns an array of IDs
111
+ def selecting_dataset(worker, n)
112
+ ds = self.table.select(self.field_primary_key.to_sym).where(self.field_id.to_sym => nil)
113
+ ds = ds.filter(self.field_end_time.to_sym => nil) if !self.field_end_time.nil?
114
+ ds = ds.filter("#{self.field_times.to_s} IS NULL OR #{self.field_times.to_s} < #{self.max_try_times.to_s}") if !self.field_times.nil?
115
+ ds.limit(n)
116
+ end # selecting_dataset
117
+
118
+ def selecting(worker, n)
119
+ if self.allowing_function.nil?
120
+ return self.selecting_dataset(worker, n).map { |o| o[self.field_primary_key.to_sym] }
121
+ else
122
+ # TODO: validar que retorna un array de strings
123
+ return self.selecting_function.call(worker, self, n)
124
+ end
125
+ end
126
+
127
+ # choose the records to retry
128
+ # returns an array of IDs
129
+ def relaunching_dataset(worker, n)
130
+ ds = self.table.select(self.field_primary_key.to_sym).where("#{self.field_time.to_s} < '#{(Time.now - 60*self.max_job_duration_minutes.to_i).strftime('%Y-%m-%d %H:%M:%S').to_s}'")
131
+ ds = ds.filter("#{self.field_end_time.to_s} IS NULL") if !self.field_end_time.nil?
132
+ # ds = ds.filter("( #{self.field_times.to_s} IS NULL OR #{self.field_times.to_s} < #{self.max_try_times.to_s} ) ") if !self.field_times.nil?
133
+ ds = ds.limit(n)
134
+ end
135
+
136
+ def relaunching(worker, n)
137
+ if self.relaunching_function.nil?
138
+ return self.relaunching_dataset(worker, n).map { |o| o[self.field_primary_key.to_sym] }
139
+ else
140
+ # TODO: validar que retorna un array de strings
141
+ return self.relaunching_function.call(worker, self, n)
142
+ end
143
+ end
144
+
145
+ def relaunch(o)
146
+ o[self.field_id.to_sym] = nil
147
+ o[self.field_time.to_sym] = nil
148
+ o[self.field_start_time.to_sym] = nil if !self.field_start_time.nil?
149
+ o[self.field_end_time.to_sym] = nil if !self.field_end_time.nil?
150
+ o.save
151
+ end
152
+
153
+ def start(o)
154
+ if self.starter_function.nil?
155
+ o[self.field_start_time.to_sym] = now() if !self.field_start_time.nil?
156
+ o[self.field_times.to_sym] = o[self.field_times.to_sym].to_i + 1 if !self.field_times.nil?
157
+ o.save
158
+ else
159
+ self.starter_function.call(o, self)
160
+ end
161
+ end
162
+
163
+ def finish(o)
164
+ if self.finisher_function.nil?
165
+ o[self.field_end_time.to_sym] = now() if !self.field_end_time.nil?
166
+ o.save
167
+ else
168
+ self.finisher_function.call(o, self)
169
+ end
170
+ end
171
+
172
+ # relaunch records
173
+ def run_relaunch(worker)
174
+ # relaunch failed records
175
+ self.relaunching(worker, self.queue_size).each { |id|
176
+ o = self.table.where(self.field_primary_key.to_sym => id).first
177
+ if self.relauncher_function.nil?
178
+ self.relaunch(o)
179
+ else
180
+ self.relauncher_function.call(o, self)
181
+ end
182
+ # release resources
183
+ DB.disconnect
184
+ GC.start
185
+ }
186
+ end # def run_relaunch
187
+
188
+ # dispatch records
189
+ # returns the # of records dispatched
190
+ def run_dispatch(worker)
191
+ # get # of available slots
192
+ n = self.available_slots(worker)
193
+
194
+ # dispatching n pending records
195
+ i = 0
196
+ if n>0
197
+ self.selecting(worker, n).each { |id|
198
+ # count the # of dispatched
199
+ i += 1
200
+ # dispatch records
201
+ o = self.table.where(self.field_primary_key.to_sym => id).first
202
+ o[self.field_id.to_sym] = worker.id
203
+ o[self.field_time.to_sym] = now()
204
+ o[self.field_start_time.to_sym] = nil if !self.field_start_time.nil?
205
+ o[self.field_end_time.to_sym] = nil if !self.field_end_time.nil?
206
+ o.save
207
+ # release resources
208
+ DB.disconnect
209
+ GC.start
210
+ }
211
+ end
212
+
213
+ #
214
+ return i
215
+ end
216
+
217
+ end # class Dispatcher
218
+
219
+ end # module BlackStack
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pampa_dispatcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Daniel Sardi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: websocket
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.8
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.2.8
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 1.2.8
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.2.8
33
+ - !ruby/object:Gem::Dependency
34
+ name: json
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.8.1
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.8.1
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 1.8.1
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.8.1
53
+ - !ruby/object:Gem::Dependency
54
+ name: tiny_tds
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 1.0.5
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.0.5
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 1.0.5
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.0.5
73
+ - !ruby/object:Gem::Dependency
74
+ name: sequel
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: 4.28.0
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 4.28.0
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.28.0
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 4.28.0
93
+ - !ruby/object:Gem::Dependency
94
+ name: pampa_workers
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: 1.1.1
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.1.1
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 1.1.1
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.1.1
113
+ description: 'THIS GEM IS STILL IN DEVELOPMENT STAGE. Find documentation here: https://github.com/leandrosardi/pampa_dispatcher.'
114
+ email: leandro.sardi@expandedventure.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - lib/pampa_dispatcher.rb
120
+ homepage: https://rubygems.org/gems/pampa_dispatcher
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.4.5.1
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: THIS GEM IS STILL IN DEVELOPMENT STAGE. Distribute work along a pool of Pampa
144
+ workers.
145
+ test_files: []