resque-scheduler 2.2.0 → 4.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/funding.yml +4 -0
  4. data/.github/workflows/codeql-analysis.yml +59 -0
  5. data/.github/workflows/rubocop.yml +27 -0
  6. data/.github/workflows/ruby.yml +81 -0
  7. data/AUTHORS.md +31 -0
  8. data/CHANGELOG.md +539 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +26 -2
  11. data/LICENSE +1 -1
  12. data/README.md +423 -91
  13. data/Rakefile +12 -35
  14. data/exe/resque-scheduler +5 -0
  15. data/lib/resque/scheduler/cli.rb +147 -0
  16. data/lib/resque/scheduler/configuration.rb +102 -0
  17. data/lib/resque/scheduler/delaying_extensions.rb +371 -0
  18. data/lib/resque/scheduler/env.rb +85 -0
  19. data/lib/resque/scheduler/extension.rb +13 -0
  20. data/lib/resque/scheduler/failure_handler.rb +11 -0
  21. data/lib/resque/scheduler/lock/base.rb +12 -3
  22. data/lib/resque/scheduler/lock/basic.rb +4 -5
  23. data/lib/resque/scheduler/lock/resilient.rb +52 -43
  24. data/lib/resque/scheduler/lock.rb +2 -1
  25. data/lib/resque/scheduler/locking.rb +104 -0
  26. data/lib/resque/scheduler/logger_builder.rb +83 -0
  27. data/lib/resque/scheduler/plugin.rb +31 -0
  28. data/lib/resque/scheduler/scheduling_extensions.rb +142 -0
  29. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +23 -8
  30. data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
  31. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +1 -1
  32. data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
  33. data/lib/resque/scheduler/server/views/search.erb +69 -0
  34. data/lib/resque/scheduler/server/views/search_form.erb +4 -0
  35. data/lib/resque/scheduler/server.rb +268 -0
  36. data/lib/resque/scheduler/signal_handling.rb +40 -0
  37. data/lib/resque/scheduler/tasks.rb +25 -0
  38. data/lib/resque/scheduler/util.rb +39 -0
  39. data/lib/resque/scheduler/version.rb +7 -0
  40. data/lib/resque/scheduler.rb +333 -149
  41. data/lib/resque-scheduler.rb +4 -0
  42. data/resque-scheduler.gemspec +56 -20
  43. metadata +205 -104
  44. data/.gitignore +0 -8
  45. data/.rubocop.yml +0 -120
  46. data/.travis.yml +0 -10
  47. data/HISTORY.md +0 -180
  48. data/lib/resque/scheduler_locking.rb +0 -90
  49. data/lib/resque_scheduler/logger_builder.rb +0 -51
  50. data/lib/resque_scheduler/plugin.rb +0 -25
  51. data/lib/resque_scheduler/server/views/scheduler.erb +0 -44
  52. data/lib/resque_scheduler/server.rb +0 -92
  53. data/lib/resque_scheduler/tasks.rb +0 -40
  54. data/lib/resque_scheduler/version.rb +0 -3
  55. data/lib/resque_scheduler.rb +0 -355
  56. data/script/migrate_to_timestamps_set.rb +0 -14
  57. data/tasks/resque_scheduler.rake +0 -2
  58. data/test/delayed_queue_test.rb +0 -383
  59. data/test/redis-test.conf +0 -108
  60. data/test/resque-web_test.rb +0 -116
  61. data/test/scheduler_args_test.rb +0 -156
  62. data/test/scheduler_hooks_test.rb +0 -23
  63. data/test/scheduler_locking_test.rb +0 -180
  64. data/test/scheduler_setup_test.rb +0 -59
  65. data/test/scheduler_test.rb +0 -256
  66. data/test/support/redis_instance.rb +0 -129
  67. data/test/test_helper.rb +0 -92
  68. /data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
data/HISTORY.md DELETED
@@ -1,180 +0,0 @@
1
- ## 2.2.0 (2013-10-13)
2
-
3
- * Locking rufus-scheduler dependency to `~> 2.0`
4
- * Updated redis dependency to `>= 3.0.0`
5
- * Add support for parameterized resque jobs.
6
- * Allowing prefix for `master_lock_key`.
7
- * Add `Resque.clean_schedules` method, which is useful when setting up the
8
- scheduler for the first time.
9
- * Bug fixes related to first time schedule retrieval and missing schedules.
10
-
11
- ## 2.1.0 (2013-09-20)
12
-
13
- * Locking to resque < 1.25.0 (for now)
14
- * Ensuring `Resque.schedule=` sets rather than appends
15
- * Process daemonization fixes including stdio redirection and redis client
16
- reconnection
17
- * Add `#scheduled_at` which returns an array of timestamps at which the
18
- specified job is scheduled
19
- * Syncing stdout/stderr
20
- * Add `#enqueue_delayed` for enqueueing specific delayed jobs immediately
21
- * Show server local time in resque-web
22
- * Enqueue immediately if job is being enqueued in the past
23
- * Using a logger instead of `#puts`, configurable via `LOGFILE`, `VERBOSE`, and
24
- `MUTE` environmental variables, as well as being settable via
25
- `Resque::Scheduler#logger`
26
- * Fixing scheduler template when arrays are passed to rufus-scheduler
27
- * Add support for configuring `Resque::Scheduler.poll_sleep_amount` via the
28
- `INTERVAL` environmental variable.
29
- * Fixed shutdown in ruby 2.0.0
30
- * Removed dependency on `Resque::Helpers`
31
-
32
- ## 2.0.1 (2013-03-20)
33
-
34
- * Adding locking to support master failover
35
- * Allow custom job classes to be used in `Resque.enqueue_at`
36
- * More efficient `#remove_delayed` implementation
37
- * Allowing `#enqueue_at` to call `#scheduled` when `Resque.inline` is `true`
38
-
39
- ## 2.0.0 (2012-05-04)
40
-
41
- * Add support for Resque.inline configuration (carlosantoniodasilva)
42
- * Fixing possible job loss race condition around deleting delayed queues
43
- and enqueuing a job 0 seconds in the future.
44
-
45
- ### 2.0.0.h (2012-03-19)
46
-
47
- * Adding plugin support with hooks (andreas)
48
-
49
- ### 2.0.0.f (2011-11-03)
50
-
51
- * TODO: address race condition with delayed jobs (using redis transactions)
52
- * Support `ENV['BACKGROUND']` flag for daemonizing (bernerdschaefer)
53
- * Added support for `before_schedule` and `after_schedule` hooks (yaauie)
54
- * Added `remove_delayed_job_from_timestamp` to remove delayed jobs from
55
- a given timestamp.
56
-
57
- ### 2.0.0.e (2011-09-16)
58
-
59
- * Adding `enqueue_at_with_queue`/`enqueue_in_with_queue` support (niralisse)
60
- * Adding `Resque::Scheduler.poll_sleep_amount` to allow for configuring
61
- the sleep time b/w delayed queue polls.
62
- * Add a "Clear Delayed Jobs" button to the Delayed Jobs page (john-griffin)
63
- * Fixed pagination issue on the Delayed tab
64
-
65
- ### 2.0.0.d (2011-04-04)
66
-
67
- * porting bug fixes from v1.9-stable
68
-
69
- ### 2.0.0.c
70
-
71
- * Rake task drop a pid file (sreeix)
72
-
73
- ### 2.0.0.b
74
-
75
- * Bug fixes
76
-
77
- ### 2.0.0.a
78
-
79
- * Dynamic schedule support (brianjlandau, davidyang)
80
- * Now depends on redis >=1.3
81
-
82
- ## 1.9.10 (2013-09-19)
83
-
84
- * Backported `#enqueue_at_with_queue`
85
- * Locking to resque < 1.25.0
86
- * Mocha setup compatibility
87
- * Ruby 1.8 compatibility in scheduler tab when schedule keys are symbols
88
-
89
- ## 1.9.9 (2011-03-29)
90
-
91
- * Compatibility with resque 1.15.0
92
-
93
- ## 1.9.8 (???)
94
-
95
- * Validates delayed jobs prior to insertion into the delayed queue (bogdan)
96
- * Rescue exceptions that occur during queuing and log them (dgrijalva)
97
-
98
- ## 1.9.7 (2010-11-09)
99
-
100
- * Support for rufus-scheduler "every" syntax (fallwith)
101
- * Ability to pass a Time to `handle_delayed_items` for testing/staging (rcarver)
102
-
103
- ## 1.9.6 (2010-10-08)
104
-
105
- * Support for custom job classes (like resque-status) (mattetti)
106
-
107
- ## 1.9.5 (2010-09-09)
108
-
109
- * Updated scheduler rake task to allow for an alternate setup task
110
- to avoid loading the entire stack. (chewbranca)
111
- * Fixed sig issue on win32 (#25)
112
-
113
- ## 1.9.4 (2010-07-29)
114
-
115
- * Adding ability to remove jobs from delayed queue (joshsz)
116
- * Fixing issue #23 (removing .present? reference)
117
-
118
- ## 1.9.3 (2010-07-07)
119
-
120
- * Bug fix (#19)
121
-
122
- ## 1.9.2 (2010-06-16)
123
-
124
- * Fixing issue with redis gem 2.0.1 and redis server 1.2.6 (dbackeus)
125
-
126
- ## 1.9.1 (2010-06-04)
127
-
128
- * Fixing issue with redis server 1.2.6 and redis gem 2.0.1
129
-
130
- ## 1.9.0 (2010-06-04)
131
-
132
- * Adding redis 2.0 support (bpo)
133
-
134
- ## 1.8.2 (2010-06-04)
135
-
136
- * Adding queue now functionality to delayed timestamps (daviddoan)
137
-
138
- ## 1.8.1 (2010-05-19)
139
-
140
- * Adding rails_env for scheduled jobs to support scoping jobs by
141
- RAILS_ENV (gravis).
142
- * Fixing ruby 1.8.6 compatibility issue.
143
- * Adding gemspec for bundler support.
144
-
145
- ## 1.8.0 (2010-04-14)
146
-
147
- * Moving version to match corresponding resque version
148
- * Sorting schedule on Scheduler tab
149
- * Adding tests for resque-web (gravis)
150
-
151
- ## 1.0.5 (2010-03-01)
152
-
153
- * Fixed support for overriding queue from schedule config.
154
- * Removed resque-web dependency on loading the job classes for "Queue Now",
155
- provided "queue" is specified in the schedule.
156
- * The queue is now stored with the job and arguments in the delayed queue so
157
- there is no longer a need for the scheduler to load job classes to introspect
158
- the queue.
159
-
160
- ## 1.0.4 (2010-02-26)
161
-
162
- * Added support for specifying the queue to put the job onto. This allows for
163
- you to have one job that can go onto multiple queues and be able to schedule
164
- jobs without having to load the job classes.
165
-
166
- ## 1.0.3 (2010-02-11)
167
-
168
- * Added support for scheduled jobs with empty crons. This is helpful to have
169
- jobs that you don't want on a schedule, but do want to be able to queue by
170
- clicking a button.
171
-
172
- ## 1.0.2 (2010-02-?)
173
-
174
- * Change Delayed Job tab to display job details if only 1 job exists
175
- for a given timestamp
176
-
177
- ## 1.0.1 (2010-01-?)
178
-
179
- * Bugfix: delayed jobs close together resulted in a 5 second sleep
180
-
@@ -1,90 +0,0 @@
1
-
2
- # ### Locking the scheduler process
3
- #
4
- # There are two places in resque-scheduler that need to be synchonized
5
- # in order to be able to run redundant scheduler processes while ensuring jobs don't
6
- # get queued multiple times when the master process changes.
7
- #
8
- # 1) Processing the delayed queues (jobs that are created from enqueue_at/enqueue_in, etc)
9
- # 2) Processing the scheduled (cron-like) jobs from rufus-scheduler
10
- #
11
- # Protecting the delayed queues (#1) is relatively easy. A simple SETNX in
12
- # redis would suffice. However, protecting the scheduled jobs is trickier
13
- # because the clocks on machines could be slightly off or actual firing times
14
- # could vary slightly due to load. If scheduler A's clock is slightly ahead
15
- # of scheduler B's clock (since they are on different machines), when
16
- # scheduler A dies, we need to ensure that scheduler B doesn't queue jobs
17
- # that A already queued before it's death. (This all assumes that it is
18
- # better to miss a few scheduled jobs than it is to run them multiple times
19
- # for the same iteration.)
20
- #
21
- # To avoid queuing multiple jobs in the case of master fail-over, the master
22
- # should remain the master as long as it can rather than a simple SETNX which
23
- # would result in the master roll being passed around frequently.
24
- #
25
- # Locking Scheme:
26
- # Each resque-scheduler process attempts to get the master lock via SETNX.
27
- # Once obtained, it sets the expiration for 3 minutes (configurable). The
28
- # master process continually updates the timeout on the lock key to be 3
29
- # minutes in the future in it's loop(s) (see `run`) and when jobs come out of
30
- # rufus-scheduler (see `load_schedule_job`). That ensures that a minimum of
31
- # 3 minutes must pass since the last queuing operation before a new master is
32
- # chosen. If, for whatever reason, the master fails to update the expiration
33
- # for 3 minutes, the key expires and the lock is up for grabs. If
34
- # miraculously the original master comes back to life, it will realize it is
35
- # no longer the master and stop processing jobs.
36
- #
37
- # The clocks on the scheduler machines can then be up to 3 minutes off from
38
- # each other without the risk of queueing the same scheduled job twice during
39
- # a master change. The catch is, in the event of a master change, no
40
- # scheduled jobs will be queued during those 3 minutes. So, there is a trade
41
- # off: the higher the timeout, the less likely scheduled jobs will be fired
42
- # twice but greater chances of missing scheduled jobs. The lower the timeout,
43
- # less likely jobs will be missed, greater the chances of jobs firing twice. If
44
- # you don't care about jobs firing twice or are certain your machines' clocks
45
- # are well in sync, a lower timeout is preferable. One thing to keep in mind:
46
- # this only effects *scheduled* jobs - delayed jobs will never be lost or
47
- # skipped since eventually a master will come online and it will process
48
- # everything that is ready (no matter how old it is). Scheduled jobs work
49
- # like cron - if you stop cron, no jobs fire while it's stopped and it doesn't
50
- # fire jobs that were missed when it starts up again.
51
-
52
- require 'resque/scheduler/lock'
53
-
54
- module Resque
55
- module SchedulerLocking
56
- def master_lock
57
- @master_lock ||= build_master_lock
58
- end
59
-
60
- def supports_lua?
61
- redis_master_version >= 2.5
62
- end
63
-
64
- def is_master?
65
- master_lock.acquire! || master_lock.locked?
66
- end
67
-
68
- def release_master_lock!
69
- master_lock.release!
70
- end
71
-
72
- private
73
-
74
- def build_master_lock
75
- if supports_lua?
76
- Resque::Scheduler::Lock::Resilient.new(master_lock_key)
77
- else
78
- Resque::Scheduler::Lock::Basic.new(master_lock_key)
79
- end
80
- end
81
-
82
- def master_lock_key
83
- "#{ENV['RESQUE_SCHEDULER_MASTER_LOCK_PREFIX'] || ''}resque_scheduler_master_lock".to_sym
84
- end
85
-
86
- def redis_master_version
87
- Resque.redis.info['redis_version'].to_f
88
- end
89
- end
90
- end
@@ -1,51 +0,0 @@
1
- module ResqueScheduler
2
- # Just builds a logger, with specified verbosity and destination.
3
- # The simplest example:
4
- #
5
- # ResqueScheduler::LoggerBuilder.new.build
6
- class LoggerBuilder
7
- # Initializes new instance of the builder
8
- #
9
- # Pass :opts Hash with
10
- # - :mute if logger needs to be silent for all levels. Default - false
11
- # - :verbose if there is a need in debug messages. Default - false
12
- # - :log_dev to output logs into a desired file. Default - STDOUT
13
- #
14
- # Example:
15
- #
16
- # LoggerBuilder.new(:mute => false, :verbose => true, :log_dev => 'log/sheduler.log')
17
- def initialize(opts={})
18
- @muted = !!opts[:mute]
19
- @verbose = !!opts[:verbose]
20
- @log_dev = opts[:log_dev] || STDOUT
21
- end
22
-
23
- # Returns an instance of Logger
24
- def build
25
- logger = Logger.new(@log_dev)
26
- logger.level = level
27
- logger.datetime_format = "%Y-%m-%d %H:%M:%S"
28
- logger.formatter = formatter
29
-
30
- logger
31
- end
32
-
33
- private
34
-
35
- def level
36
- if @verbose && !@muted
37
- Logger::DEBUG
38
- elsif !@muted
39
- Logger::INFO
40
- else
41
- Logger::FATAL
42
- end
43
- end
44
-
45
- def formatter
46
- proc do |severity, datetime, progname, msg|
47
- "[#{severity}] #{datetime}: #{msg}\n"
48
- end
49
- end
50
- end
51
- end
@@ -1,25 +0,0 @@
1
- module ResqueScheduler
2
- module Plugin
3
- extend self
4
- def hooks(job, pattern)
5
- job.methods.grep(/^#{pattern}/).sort
6
- end
7
-
8
- def run_hooks(job, pattern, *args)
9
- results = hooks(job, pattern).collect do |hook|
10
- job.send(hook, *args)
11
- end
12
-
13
- results.all? { |result| result != false }
14
- end
15
-
16
- def method_missing(method_name, *args, &block)
17
- if method_name.to_s =~ /^run_(.*)_hooks$/
18
- job = args.shift
19
- run_hooks job, $1, *args
20
- else
21
- super
22
- end
23
- end
24
- end
25
- end
@@ -1,44 +0,0 @@
1
- <h1>Schedule</h1>
2
-
3
- <p class='intro'>
4
- The list below contains all scheduled jobs. Click &quot;Queue now&quot; to queue
5
- a job immediately.
6
- Server local time: <%= Time.now %>
7
- </p>
8
-
9
- <table>
10
- <tr>
11
- <th></th>
12
- <th>Name</th>
13
- <th>Description</th>
14
- <th>Interval</th>
15
- <th>Class</th>
16
- <th>Queue</th>
17
- <th>Arguments</th>
18
- </tr>
19
- <% Resque.schedule.keys.sort.each do |name| %>
20
- <% config = Resque.schedule[name] %>
21
- <tr>
22
- <td>
23
- <form action="<%= u "/schedule/requeue" %>" method="post">
24
- <input type="hidden" name="job_name" value="<%= h name %>">
25
- <input type="submit" value="Queue now">
26
- </form>
27
- </td>
28
- <td><%= h name %></td>
29
- <td><%= h config['description'] %></td>
30
- <td style="white-space:nowrap"><%= if !config['every'].nil?
31
- h('every: ' + (config['every'].is_a?(Array) ? config['every'].join(", ") : config['every'].to_s ))
32
- elsif !config['cron'].nil?
33
- h('cron: ' + config['cron'].to_s)
34
- else
35
- 'Not currently scheduled'
36
- end %></td>
37
- <td><%= (config['class'].nil? && !config['custom_job_class'].nil?) ?
38
- h(config['custom_job_class']) :
39
- h(config['class']) %></td>
40
- <td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
41
- <td><%= h config['args'].inspect %></td>
42
- </tr>
43
- <% end %>
44
- </table>
@@ -1,92 +0,0 @@
1
- require 'resque_scheduler'
2
- require 'resque/server'
3
-
4
- # Extend Resque::Server to add tabs
5
- module ResqueScheduler
6
-
7
- module Server
8
-
9
- def self.included(base)
10
-
11
- base.class_eval do
12
-
13
- helpers do
14
- def format_time(t)
15
- t.strftime("%Y-%m-%d %H:%M:%S %z")
16
- end
17
-
18
- def queue_from_class_name(class_name)
19
- Resque.queue_from_class(Resque.constantize(class_name))
20
- end
21
- end
22
-
23
- get "/schedule" do
24
- Resque.reload_schedule! if Resque::Scheduler.dynamic
25
- # Is there a better way to specify alternate template locations with sinatra?
26
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/scheduler.erb'))
27
- end
28
-
29
- post "/schedule/requeue" do
30
- @job_name = params['job_name'] || params[:job_name]
31
- config = Resque.schedule[@job_name]
32
- @parameters = config['parameters'] || config[:parameters]
33
- if @parameters
34
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/requeue-params.erb'))
35
- else
36
- Resque::Scheduler.enqueue_from_config(config)
37
- redirect u("/overview")
38
- end
39
- end
40
-
41
- post "/schedule/requeue_with_params" do
42
- job_name = params['job_name'] || params[:job_name]
43
- config = Resque.schedule[job_name]
44
- # Build args hash from post data (removing the job name)
45
- submitted_args = params.reject {|key, value| key == 'job_name' || key == :job_name}
46
-
47
- # Merge constructed args hash with existing args hash for
48
- # the job, if it exists
49
- config_args = config['args'] || config[:args] || {}
50
- config_args = config_args.merge(submitted_args)
51
-
52
- # Insert the args hash into config and queue the resque job
53
- config = config.merge({'args' => config_args})
54
- Resque::Scheduler.enqueue_from_config(config)
55
- redirect u("/overview")
56
- end
57
-
58
- get "/delayed" do
59
- # Is there a better way to specify alternate template locations with sinatra?
60
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed.erb'))
61
- end
62
-
63
- get "/delayed/:timestamp" do
64
- # Is there a better way to specify alternate template locations with sinatra?
65
- erb File.read(File.join(File.dirname(__FILE__), 'server/views/delayed_timestamp.erb'))
66
- end
67
-
68
- post "/delayed/queue_now" do
69
- timestamp = params['timestamp']
70
- Resque::Scheduler.enqueue_delayed_items_for_timestamp(timestamp.to_i) if timestamp.to_i > 0
71
- redirect u("/overview")
72
- end
73
-
74
- post "/delayed/clear" do
75
- Resque.reset_delayed_queue
76
- redirect u('delayed')
77
- end
78
-
79
- end
80
-
81
- end
82
-
83
- Resque::Server.tabs << 'Schedule'
84
- Resque::Server.tabs << 'Delayed'
85
-
86
- end
87
-
88
- end
89
-
90
- Resque::Server.class_eval do
91
- include ResqueScheduler::Server
92
- end
@@ -1,40 +0,0 @@
1
- require 'resque/tasks'
2
- # will give you the resque tasks
3
-
4
- namespace :resque do
5
- task :setup
6
-
7
- desc "Start Resque Scheduler"
8
- task :scheduler => :scheduler_setup do
9
- require 'resque'
10
- require 'resque_scheduler'
11
-
12
- # Need to set this here for conditional Process.daemon redirect of stderr/stdout to /dev/null
13
- Resque::Scheduler.mute = true if ENV['MUTE']
14
-
15
- if ENV['BACKGROUND']
16
- unless Process.respond_to?('daemon')
17
- abort "env var BACKGROUND is set, which requires ruby >= 1.9"
18
- end
19
- Process.daemon(true, !Resque::Scheduler.mute)
20
- Resque.redis.client.reconnect
21
- end
22
-
23
- File.open(ENV['PIDFILE'], 'w') { |f| f << Process.pid.to_s } if ENV['PIDFILE']
24
-
25
- Resque::Scheduler.dynamic = true if ENV['DYNAMIC_SCHEDULE']
26
- Resque::Scheduler.verbose = true if ENV['VERBOSE']
27
- Resque::Scheduler.logfile = ENV['LOGFILE'] if ENV['LOGFILE']
28
- Resque::Scheduler.poll_sleep_amount = Integer(ENV['INTERVAL']) if ENV['INTERVAL']
29
- Resque::Scheduler.run
30
- end
31
-
32
- task :scheduler_setup do
33
- if ENV['INITIALIZER_PATH']
34
- load ENV['INITIALIZER_PATH'].to_s.strip
35
- else
36
- Rake::Task['resque:setup'].invoke
37
- end
38
- end
39
-
40
- end
@@ -1,3 +0,0 @@
1
- module ResqueScheduler
2
- VERSION = '2.2.0'
3
- end