fiber_job 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +3 -22
- data/bin/fiber_job +54 -12
- data/lib/fiber_job/concurrency.rb +45 -4
- data/lib/fiber_job/config.rb +67 -2
- data/lib/fiber_job/job.rb +7 -2
- data/lib/fiber_job/process_manager.rb +85 -9
- data/lib/fiber_job/queue.rb +10 -2
- data/lib/fiber_job/version.rb +1 -1
- data/lib/fiber_job/worker.rb +22 -20
- data/lib/fiber_job.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abd9dec492a67e4dc7c0e935e51fde3eaeae0d328555bba8cd35f617c83d65f1
|
4
|
+
data.tar.gz: af960358e61a9a743afaf14d23a909392cc942fdd76e3070e6ef8ae63a828255
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06c83f8bd53d0d9a4319caffdc21336557eb976633a6d81140d0ed2c1f235c6bc396e1e6ee643666ea933bb5ef673d167ef769d35a42d8d0d752cd779a0b975d
|
7
|
+
data.tar.gz: 6e03a7dab3a23f19b78716f0aad0077d355b012458fbaa9a661ce2f90622f296c710f07dbf9d2b29c8970660e05980e3dc3c80aa9a7f342fff449c2290cdf99f
|
data/README.md
CHANGED
@@ -2,24 +2,10 @@
|
|
2
2
|
|
3
3
|
A high-performance, Redis-based background job processing library for Ruby built on modern fiber-based concurrency. FiberJob combines the persistence of Redis with the speed of async fibers to deliver exceptional performance and reliability.
|
4
4
|
|
5
|
-
##
|
6
|
-
|
7
|
-
FiberJob is a experimental gem that uses a architecture that sets it apart from traditional job queues:
|
8
|
-
|
9
|
-
### **Hybrid Redis + Async::Queue Design**
|
10
|
-
- **Redis for persistence**: Durable job storage with atomic operations and scheduling
|
11
|
-
- **Async::Queue for speed**: Lightning-fast in-memory job processing with fiber-based concurrency
|
12
|
-
- **Best of both worlds**: Reliability of Redis + performance of in-memory queues
|
13
|
-
|
14
|
-
### **Advanced Fiber Management**
|
15
|
-
- **Separation of concerns**: Independent polling fibers fetch from Redis while processing fibers execute jobs
|
16
|
-
- **Per-queue fiber pools**: Isolated concurrency control with `Async::Semaphore` for optimal resource utilization
|
17
|
-
- **Non-blocking operations**: All I/O operations use async/await patterns for maximum throughput
|
5
|
+
## Requirements
|
18
6
|
|
19
|
-
|
20
|
-
-
|
21
|
-
- **Fast job execution**: Jobs flow through in-memory `Async::Queue` for sub-millisecond processing
|
22
|
-
- **Scalable concurrency**: Configurable fiber pools scale efficiently without thread overhead
|
7
|
+
- Ruby 3.1+
|
8
|
+
- Redis 5.0+
|
23
9
|
|
24
10
|
## Features
|
25
11
|
|
@@ -266,11 +252,6 @@ end
|
|
266
252
|
- `REDIS_URL`: Redis connection URL
|
267
253
|
- `FIBER_JOB_LOG_LEVEL`: Logging level
|
268
254
|
|
269
|
-
## Requirements
|
270
|
-
|
271
|
-
- Ruby 3.1+
|
272
|
-
- Redis 5.0+
|
273
|
-
|
274
255
|
## License
|
275
256
|
|
276
257
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
data/bin/fiber_job
CHANGED
@@ -2,29 +2,71 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative '../lib/fiber_job'
|
5
|
+
require 'optparse'
|
5
6
|
|
6
|
-
#
|
7
|
-
|
7
|
+
# CLI for FiberJob with configuration file support
|
8
|
+
def load_configuration_file(config_path)
|
9
|
+
unless File.exist?(config_path)
|
10
|
+
puts "Error: Configuration file not found: #{config_path}"
|
11
|
+
exit 1
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
begin
|
15
|
+
load config_path
|
16
|
+
rescue => e
|
17
|
+
puts "Error loading configuration file: #{e.message}"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_help
|
16
23
|
puts <<~HELP
|
17
24
|
FiberJob - High-performance fiber-based job processing
|
18
25
|
|
19
26
|
Usage:
|
20
|
-
fiber_job
|
27
|
+
fiber_job start [options] Start the worker server
|
21
28
|
fiber_job version Show version
|
22
29
|
fiber_job help Show this help
|
23
30
|
|
31
|
+
Options:
|
32
|
+
-c, --config FILE Path to configuration file
|
33
|
+
|
24
34
|
Examples:
|
25
|
-
fiber_job
|
26
|
-
|
35
|
+
fiber_job start # Start with default configuration
|
36
|
+
fiber_job start -c config.rb # Start with custom configuration
|
37
|
+
REDIS_URL=redis://localhost:6379/1 fiber_job start # Environment override
|
27
38
|
HELP
|
39
|
+
end
|
40
|
+
|
41
|
+
# Parse command and options
|
42
|
+
command = ARGV[0]
|
43
|
+
config_file = nil
|
44
|
+
|
45
|
+
case command
|
46
|
+
when 'start'
|
47
|
+
# Parse options for start command
|
48
|
+
OptionParser.new do |opts|
|
49
|
+
opts.on('-c', '--config FILE', 'Configuration file path') do |file|
|
50
|
+
config_file = file
|
51
|
+
end
|
52
|
+
opts.on('-h', '--help', 'Show help') do
|
53
|
+
show_help
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
end.parse!(ARGV[1..-1])
|
57
|
+
|
58
|
+
# Load configuration file if specified
|
59
|
+
load_configuration_file(config_file) if config_file
|
60
|
+
|
61
|
+
puts "Starting FiberJob server..."
|
62
|
+
FiberJob::ProcessManager.start_worker
|
63
|
+
|
64
|
+
when 'version'
|
65
|
+
puts "FiberJob version #{FiberJob::VERSION}"
|
66
|
+
|
67
|
+
when nil, 'help', '-h', '--help'
|
68
|
+
show_help
|
69
|
+
|
28
70
|
else
|
29
71
|
puts "Unknown command: #{command}"
|
30
72
|
puts "Run 'fiber_job help' for usage information"
|
@@ -4,15 +4,56 @@ require 'async'
|
|
4
4
|
require 'async/semaphore'
|
5
5
|
|
6
6
|
module FiberJob
|
7
|
-
# The ConcurrencyManager class
|
8
|
-
#
|
7
|
+
# The ConcurrencyManager class provides efficient fiber reuse through direct
|
8
|
+
# semaphore acquire/release operations instead of spawning new fibers
|
9
9
|
class ConcurrencyManager
|
10
|
+
attr_reader :semaphore
|
11
|
+
|
10
12
|
def initialize(max_concurrency: 5)
|
11
13
|
@semaphore = Async::Semaphore.new(max_concurrency)
|
12
14
|
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
+
# Acquire a semaphore permit, blocking if none available
|
17
|
+
# @return [void]
|
18
|
+
def acquire
|
19
|
+
@semaphore.acquire
|
20
|
+
end
|
21
|
+
|
22
|
+
# Release a semaphore permit, allowing waiting fibers to proceed
|
23
|
+
# @return [void]
|
24
|
+
def release
|
25
|
+
@semaphore.release
|
26
|
+
end
|
27
|
+
|
28
|
+
# Execute a block with automatic acquire/release handling
|
29
|
+
# This method reuses the current fiber instead of spawning new ones
|
30
|
+
# @yield [void] Block to execute within semaphore protection
|
31
|
+
# @return [Object] Result of the block execution
|
32
|
+
def execute(&)
|
33
|
+
acquire
|
34
|
+
begin
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
release
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Check if the semaphore would block on acquire
|
42
|
+
# @return [Boolean] true if acquire would block
|
43
|
+
def blocking?
|
44
|
+
@semaphore.blocking?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Current number of acquired permits
|
48
|
+
# @return [Integer] Number of active permits
|
49
|
+
def count
|
50
|
+
@semaphore.count
|
51
|
+
end
|
52
|
+
|
53
|
+
# Maximum number of permits
|
54
|
+
# @return [Integer] Semaphore limit
|
55
|
+
def limit
|
56
|
+
@semaphore.limit
|
16
57
|
end
|
17
58
|
end
|
18
59
|
end
|
data/lib/fiber_job/config.rb
CHANGED
@@ -24,6 +24,11 @@ module FiberJob
|
|
24
24
|
# }
|
25
25
|
# end
|
26
26
|
#
|
27
|
+
# @example Job auto-loading
|
28
|
+
# FiberJob.configure do |config|
|
29
|
+
# config.job_paths = ['app/jobs', 'lib/jobs']
|
30
|
+
# end
|
31
|
+
#
|
27
32
|
class Config
|
28
33
|
# @!attribute [rw] redis_url
|
29
34
|
# @return [String] Redis connection URL
|
@@ -37,7 +42,9 @@ module FiberJob
|
|
37
42
|
# @return [Logger] Logger instance for application logging
|
38
43
|
# @!attribute [rw] log_level
|
39
44
|
# @return [Symbol] Logging level (:debug, :info, :warn, :error)
|
40
|
-
|
45
|
+
# @!attribute [rw] job_paths
|
46
|
+
# @return [Array<String>] List of paths to auto-load job classes from
|
47
|
+
attr_accessor :redis_url, :concurrency, :queues, :queue_concurrency, :logger, :log_level, :job_paths
|
41
48
|
|
42
49
|
# Initializes configuration with sensible defaults.
|
43
50
|
# Values can be overridden through environment variables or configuration blocks.
|
@@ -49,12 +56,13 @@ module FiberJob
|
|
49
56
|
# - FIBER_JOB_LOG_LEVEL: Logging level (default: info)
|
50
57
|
def initialize
|
51
58
|
@redis_url = ENV['REDIS_URL'] || 'redis://localhost:6379'
|
52
|
-
@concurrency = 2
|
59
|
+
@concurrency = 2
|
53
60
|
@queues = [:default]
|
54
61
|
@queue_concurrency = { default: 2 } # Per-queue concurrency
|
55
62
|
@log_level = ENV['FIBER_JOB_LOG_LEVEL']&.to_sym || :info
|
56
63
|
@logger = ::Logger.new($stdout)
|
57
64
|
@logger.level = ::Logger.const_get(@log_level.to_s.upcase)
|
65
|
+
@job_paths = []
|
58
66
|
end
|
59
67
|
|
60
68
|
# Returns the concurrency setting for a specific queue.
|
@@ -70,5 +78,62 @@ module FiberJob
|
|
70
78
|
def concurrency_for_queue(queue_name)
|
71
79
|
@queue_concurrency[queue_name.to_sym] || @concurrency
|
72
80
|
end
|
81
|
+
|
82
|
+
# Auto-loads job classes from configured paths.
|
83
|
+
# Recursively loads all .rb files in the specified directories
|
84
|
+
# and validates that they contain classes inheriting from FiberJob::Job.
|
85
|
+
#
|
86
|
+
# @return [Array<Class>] List of loaded job classes
|
87
|
+
#
|
88
|
+
# @example Auto-load jobs
|
89
|
+
# config.job_paths = ['app/jobs', 'lib/jobs']
|
90
|
+
# loaded_jobs = config.load_jobs!
|
91
|
+
# # => [EmailJob, DataProcessingJob, ...]
|
92
|
+
def load_jobs!
|
93
|
+
loaded_classes = []
|
94
|
+
|
95
|
+
@job_paths.each do |path|
|
96
|
+
unless Dir.exist?(path)
|
97
|
+
@logger.warn "Job path does not exist: #{path}"
|
98
|
+
next
|
99
|
+
end
|
100
|
+
|
101
|
+
@logger.info "Loading jobs from: #{path}"
|
102
|
+
|
103
|
+
Dir.glob("#{path}/**/*.rb").sort.each do |file|
|
104
|
+
begin
|
105
|
+
# Track classes before requiring the file
|
106
|
+
classes_before = job_classes
|
107
|
+
|
108
|
+
require_relative File.expand_path(file)
|
109
|
+
|
110
|
+
# Find newly loaded job classes
|
111
|
+
new_classes = job_classes - classes_before
|
112
|
+
|
113
|
+
new_classes.each do |job_class|
|
114
|
+
@logger.debug "Loaded job class: #{job_class}"
|
115
|
+
loaded_classes << job_class
|
116
|
+
end
|
117
|
+
|
118
|
+
rescue => e
|
119
|
+
@logger.error "Failed to load job file #{file}: #{e.message}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
@logger.info "Loaded #{loaded_classes.size} job classes"
|
125
|
+
loaded_classes
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Returns all classes that inherit from FiberJob::Job
|
131
|
+
def job_classes
|
132
|
+
return [] unless defined?(FiberJob::Job)
|
133
|
+
|
134
|
+
ObjectSpace.each_object(Class).select do |klass|
|
135
|
+
klass < FiberJob::Job
|
136
|
+
end
|
137
|
+
end
|
73
138
|
end
|
74
139
|
end
|
data/lib/fiber_job/job.rb
CHANGED
@@ -43,13 +43,18 @@ module FiberJob
|
|
43
43
|
# @return [Integer] Job priority (higher numbers = higher priority)
|
44
44
|
# @!attribute [rw] timeout
|
45
45
|
# @return [Integer] Maximum execution time in seconds before timeout
|
46
|
+
# @!attribute [r] config
|
47
|
+
# @return [FiberJob::Config] Configuration object for this job instance
|
46
48
|
attr_accessor :queue, :retry_count, :max_retries, :priority, :timeout
|
49
|
+
attr_reader :config
|
47
50
|
|
48
51
|
# Initializes a new job instance with default configuration.
|
49
|
-
#
|
52
|
+
# Uses centralized configuration for defaults instead of hardcoded values.
|
50
53
|
#
|
54
|
+
# @param config [FiberJob::Config, nil] Configuration object to use for defaults
|
51
55
|
# @return [void]
|
52
|
-
def initialize
|
56
|
+
def initialize(config: nil)
|
57
|
+
@config = config || FiberJob.config
|
53
58
|
@queue = :default
|
54
59
|
@retry_count = 0
|
55
60
|
@max_retries = 3
|
@@ -1,18 +1,94 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiberJob
|
4
|
-
# ProcessManager is
|
5
|
-
#
|
4
|
+
# ProcessManager is the single entry point for running FiberJob workers.
|
5
|
+
# It manages worker lifecycle, configuration, signal handling, and graceful shutdown.
|
6
|
+
#
|
7
|
+
# This is the recommended way to start FiberJob workers and should be used
|
8
|
+
# instead of directly instantiating Worker objects.
|
9
|
+
#
|
10
|
+
# @example Start with default configuration
|
11
|
+
# FiberJob::ProcessManager.start_worker
|
12
|
+
#
|
13
|
+
# @example Start with custom configuration
|
14
|
+
# config = FiberJob::Config.new
|
15
|
+
# config.concurrency = 10
|
16
|
+
# FiberJob::ProcessManager.start_worker(config: config)
|
17
|
+
#
|
6
18
|
class ProcessManager
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
19
|
+
class << self
|
20
|
+
# Starts a FiberJob worker process with proper signal handling and lifecycle management.
|
21
|
+
# This is the main entry point for running FiberJob workers.
|
22
|
+
#
|
23
|
+
# @param config [FiberJob::Config, nil] Configuration object (defaults to FiberJob.config)
|
24
|
+
# @param queues [Array<Symbol>, nil] Queue names to process (defaults to config.queues)
|
25
|
+
# @param concurrency [Integer, nil] Concurrency level (defaults to config.concurrency)
|
26
|
+
# @return [void]
|
27
|
+
#
|
28
|
+
# @example Basic usage
|
29
|
+
# FiberJob::ProcessManager.start_worker
|
30
|
+
#
|
31
|
+
# @example With custom configuration
|
32
|
+
# FiberJob::ProcessManager.start_worker(
|
33
|
+
# config: my_config,
|
34
|
+
# queues: [:high_priority, :default],
|
35
|
+
# concurrency: 8
|
36
|
+
# )
|
37
|
+
def start_worker(config: nil, queues: nil, concurrency: nil)
|
38
|
+
config ||= FiberJob.config
|
39
|
+
queues ||= config.queues
|
40
|
+
concurrency ||= config.concurrency
|
11
41
|
|
12
|
-
|
13
|
-
|
42
|
+
# Auto-load job classes if paths are configured
|
43
|
+
config.load_jobs! if config.job_paths&.any?
|
44
|
+
|
45
|
+
log_startup_info(config, queues, concurrency)
|
46
|
+
|
47
|
+
worker = create_worker(config: config, queues: queues, concurrency: concurrency)
|
48
|
+
setup_signal_handlers(worker)
|
49
|
+
|
50
|
+
begin
|
51
|
+
worker.start
|
52
|
+
rescue => e
|
53
|
+
config.logger.error "Failed to start FiberJob worker: #{e.message}"
|
54
|
+
config.logger.error e.backtrace.join("\n")
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
14
58
|
|
15
|
-
|
59
|
+
private
|
60
|
+
|
61
|
+
# Creates and configures a new worker instance
|
62
|
+
def create_worker(config:, queues:, concurrency:)
|
63
|
+
Worker.new(config: config, queues: queues, concurrency: concurrency)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sets up signal handlers for graceful shutdown
|
67
|
+
def setup_signal_handlers(worker)
|
68
|
+
%w[INT TERM].each do |signal|
|
69
|
+
trap(signal) do
|
70
|
+
worker.config.logger.info "Received #{signal}, shutting down gracefully..."
|
71
|
+
worker.stop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Logs startup information for debugging and monitoring
|
77
|
+
def log_startup_info(config, queues, concurrency)
|
78
|
+
logger = config.logger
|
79
|
+
logger.info "Starting FiberJob worker server"
|
80
|
+
logger.info "Redis URL: #{config.redis_url}"
|
81
|
+
logger.info "Queues: #{queues.join(', ')}"
|
82
|
+
logger.info "Global concurrency: #{concurrency}"
|
83
|
+
|
84
|
+
queues.each do |queue|
|
85
|
+
queue_concurrency = config.concurrency_for_queue(queue)
|
86
|
+
worker_fibers = [queue_concurrency, 10].min
|
87
|
+
logger.info "Queue '#{queue}' concurrency: #{queue_concurrency} (#{worker_fibers} worker fibers)"
|
88
|
+
end
|
89
|
+
|
90
|
+
logger.info "Log level: #{config.log_level}"
|
91
|
+
end
|
16
92
|
end
|
17
93
|
end
|
18
94
|
end
|
data/lib/fiber_job/queue.rb
CHANGED
@@ -27,12 +27,20 @@ module FiberJob
|
|
27
27
|
#
|
28
28
|
# @see FiberJob::Worker
|
29
29
|
class Queue
|
30
|
+
class << self
|
31
|
+
attr_writer :config
|
32
|
+
|
33
|
+
def config
|
34
|
+
@config ||= FiberJob.config
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
30
38
|
# Returns the shared Redis connection instance.
|
31
39
|
# Creates a new connection if one doesn't exist.
|
32
40
|
#
|
33
41
|
# @return [Redis] The shared Redis connection
|
34
42
|
def self.redis
|
35
|
-
@redis ||= Redis.new(url:
|
43
|
+
@redis ||= Redis.new(url: config.redis_url)
|
36
44
|
end
|
37
45
|
|
38
46
|
# Creates a new Redis connection for fiber-safe operations.
|
@@ -41,7 +49,7 @@ module FiberJob
|
|
41
49
|
# @return [Redis] A new Redis connection instance
|
42
50
|
def self.redis_connection
|
43
51
|
# Create a new Redis connection for fiber-safe operations
|
44
|
-
Redis.new(url:
|
52
|
+
Redis.new(url: config.redis_url)
|
45
53
|
end
|
46
54
|
|
47
55
|
# Adds a job to the specified queue for immediate processing.
|
data/lib/fiber_job/version.rb
CHANGED
data/lib/fiber_job/worker.rb
CHANGED
@@ -4,13 +4,26 @@ require 'async'
|
|
4
4
|
require 'async/queue'
|
5
5
|
|
6
6
|
module FiberJob
|
7
|
+
# Worker handles the actual job processing using fiber-based concurrency.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
# This class is intended for internal use by ProcessManager and should not
|
11
|
+
# be instantiated directly. Use FiberJob::ProcessManager.start_worker instead.
|
12
|
+
#
|
13
|
+
# @see FiberJob::ProcessManager
|
7
14
|
class Worker
|
8
|
-
|
9
|
-
|
10
|
-
|
15
|
+
attr_reader :config
|
16
|
+
|
17
|
+
def initialize(config: nil, queues: nil, concurrency: nil)
|
18
|
+
@config = config || FiberJob.config
|
19
|
+
@queues = queues || @config.queues
|
20
|
+
@concurrency = concurrency || @config.concurrency
|
11
21
|
@running = false
|
12
22
|
@managers = {}
|
13
23
|
@job_queues = {} # In-memory Async::Queue per Redis queue
|
24
|
+
|
25
|
+
# Inject configuration into Queue class
|
26
|
+
Queue.config = @config
|
14
27
|
end
|
15
28
|
|
16
29
|
def start
|
@@ -20,20 +33,19 @@ module FiberJob
|
|
20
33
|
# Initialize all queues first
|
21
34
|
@queues.each do |queue_name|
|
22
35
|
@job_queues[queue_name] = Async::Queue.new
|
23
|
-
queue_concurrency =
|
36
|
+
queue_concurrency = @config.concurrency_for_queue(queue_name)
|
24
37
|
@managers[queue_name] = ConcurrencyManager.new(max_concurrency: queue_concurrency)
|
25
38
|
end
|
26
39
|
|
27
|
-
# Start independent pollers for each queue
|
28
40
|
@queues.each do |queue_name|
|
29
41
|
task.async do
|
30
42
|
poll_redis_queue(queue_name)
|
31
43
|
end
|
32
44
|
end
|
33
45
|
|
34
|
-
# Start
|
46
|
+
# Start fixed worker fiber pools for efficient fiber reuse
|
35
47
|
@queues.each do |queue_name|
|
36
|
-
queue_concurrency =
|
48
|
+
queue_concurrency = @config.concurrency_for_queue(queue_name) || @concurrency
|
37
49
|
queue_concurrency.times do
|
38
50
|
task.async do
|
39
51
|
process_job_queue(queue_name)
|
@@ -41,7 +53,6 @@ module FiberJob
|
|
41
53
|
end
|
42
54
|
end
|
43
55
|
|
44
|
-
# Global support fibers
|
45
56
|
task.async do
|
46
57
|
process_scheduled_jobs
|
47
58
|
end
|
@@ -62,36 +73,31 @@ module FiberJob
|
|
62
73
|
private
|
63
74
|
|
64
75
|
# Single poller fiber that fetches jobs from Redis and distributes to workers
|
65
|
-
# This eliminates Redis brpop contention by having only one fiber per queue accessing Redis
|
66
76
|
def poll_redis_queue(queue_name)
|
67
|
-
# Create dedicated Redis connection for this poller to avoid blocking other pollers
|
68
77
|
redis_conn = Queue.redis_connection
|
69
78
|
while @running
|
70
79
|
begin
|
71
|
-
# Use longer timeout since we're the only poller - no contention
|
72
80
|
job_data = Queue.pop(queue_name, timeout: 1.0, redis_conn: redis_conn)
|
73
81
|
|
74
82
|
if job_data
|
75
|
-
# Push to in-memory queue for worker fibers to process
|
76
83
|
@job_queues[queue_name].push(job_data)
|
77
84
|
end
|
78
85
|
rescue StandardError => e
|
79
86
|
FiberJob.logger.error "Redis polling error for queue #{queue_name}: #{e.message}"
|
80
|
-
sleep(1)
|
87
|
+
sleep(1)
|
81
88
|
end
|
82
89
|
end
|
83
90
|
end
|
84
91
|
|
85
92
|
# Worker fibers process jobs from the fast in-memory queue
|
86
|
-
#
|
93
|
+
# Each fiber reuses itself by acquiring/releasing semaphore permits
|
87
94
|
def process_job_queue(queue_name)
|
88
95
|
while @running
|
89
96
|
begin
|
90
|
-
# Fast in-memory dequeue operation
|
91
97
|
job_data = @job_queues[queue_name].dequeue
|
92
98
|
|
93
99
|
if job_data
|
94
|
-
#
|
100
|
+
# Reuse current fiber with semaphore-controlled concurrency
|
95
101
|
@managers[queue_name].execute do
|
96
102
|
execute_job(job_data)
|
97
103
|
end
|
@@ -111,10 +117,6 @@ module FiberJob
|
|
111
117
|
|
112
118
|
job.retry_count = job_data['retry_count'] || 0
|
113
119
|
|
114
|
-
# if job.retry_count > 0
|
115
|
-
# FiberJob.logger.info "Executing #{job_class} (retry #{job.retry_count}/#{job.max_retries})"
|
116
|
-
# end
|
117
|
-
|
118
120
|
begin
|
119
121
|
Timeout.timeout(job.timeout) do
|
120
122
|
args = (job_data['args'] || []).dup
|
data/lib/fiber_job.rb
CHANGED