pampa_dispatcher 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []