pitchfork 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'pitchfork/pitchfork_http'
3
+ require 'pitchfork/flock'
3
4
 
4
5
  module Pitchfork
5
6
  # This is the process manager of Pitchfork. This manages worker
@@ -9,11 +10,11 @@ module Pitchfork
9
10
  class HttpServer
10
11
  # :stopdoc:
11
12
  attr_accessor :app, :timeout, :worker_processes,
12
- :before_fork, :after_fork,
13
+ :after_fork, :after_promotion,
13
14
  :listener_opts, :children,
14
15
  :orig_app, :config, :ready_pipe,
15
16
  :default_middleware, :early_hints
16
- attr_writer :after_worker_exit, :after_worker_ready, :refork_condition, :mold_selector
17
+ attr_writer :after_worker_exit, :after_worker_ready, :refork_condition
17
18
 
18
19
  attr_reader :logger
19
20
  include Pitchfork::SocketHelper
@@ -27,7 +28,6 @@ module Pitchfork
27
28
  NOOP = '.'
28
29
 
29
30
  REFORKING_AVAILABLE = Pitchfork::CHILD_SUBREAPER_AVAILABLE || Process.pid == 1
30
- MAX_SLEEP = 1 # seconds
31
31
 
32
32
  # :startdoc:
33
33
  # This Hash is considered a stable interface and changing its contents
@@ -60,13 +60,15 @@ module Pitchfork
60
60
  # HttpServer.run.join to join the thread that's processing
61
61
  # incoming requests on the socket.
62
62
  def initialize(app, options = {})
63
+ @exit_status = 0
63
64
  @app = app
64
65
  @respawn = false
65
66
  @last_check = time_now
66
- @default_middleware = true
67
+ @promotion_lock = Flock.new("pitchfork-promotion")
68
+
67
69
  options = options.dup
68
70
  @ready_pipe = options.delete(:ready_pipe)
69
- @init_listeners = options[:listeners] ? options[:listeners].dup : []
71
+ @init_listeners = options[:listeners].dup || []
70
72
  options[:use_defaults] = true
71
73
  self.config = Pitchfork::Configurator.new(options)
72
74
  self.listener_opts = {}
@@ -104,7 +106,7 @@ module Pitchfork
104
106
  end
105
107
 
106
108
  # Runs the thing. Returns self so you can run join on it
107
- def start
109
+ def start(sync = true)
108
110
  Pitchfork.enable_child_subreaper # noop if not supported
109
111
 
110
112
  # This socketpair is used to wake us up from select(2) in #join when signals
@@ -120,7 +122,6 @@ module Pitchfork
120
122
  @queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
121
123
  trap(:CHLD) { awaken_master }
122
124
 
123
- bind_listeners!
124
125
  if REFORKING_AVAILABLE
125
126
  spawn_initial_mold
126
127
  wait_for_pending_workers
@@ -129,13 +130,17 @@ module Pitchfork
129
130
  end
130
131
  else
131
132
  build_app!
133
+ bind_listeners!
134
+ after_promotion.call(self, Worker.new(nil, pid: $$).promoted!)
132
135
  end
133
136
 
134
- spawn_missing_workers
135
- # We could just return here as we'd register them later in #join.
136
- # However a good part of the test suite assumes #start only return
137
- # once all initial workers are spawned.
138
- wait_for_pending_workers
137
+ if sync
138
+ spawn_missing_workers
139
+ # We could just return here as we'd register them later in #join.
140
+ # However a good part of the test suite assumes #start only return
141
+ # once all initial workers are spawned.
142
+ wait_for_pending_workers
143
+ end
139
144
 
140
145
  self
141
146
  end
@@ -237,10 +242,19 @@ module Pitchfork
237
242
  end
238
243
  stop # gracefully shutdown all workers on our way out
239
244
  logger.info "master complete"
245
+ @exit_status
240
246
  end
241
247
 
242
248
  def monitor_loop(sleep = true)
243
249
  reap_all_workers
250
+
251
+ if REFORKING_AVAILABLE && @respawn && @children.molds.empty?
252
+ logger.info("No mold alive, shutting down")
253
+ @exit_status = 1
254
+ @sig_queue << :QUIT
255
+ @respawn = false
256
+ end
257
+
244
258
  case message = @sig_queue.shift
245
259
  when nil
246
260
  # avoid murdering workers after our master process (or the
@@ -253,13 +267,15 @@ module Pitchfork
253
267
  end
254
268
  if @respawn
255
269
  maintain_worker_count
256
- automatically_refork_workers if REFORKING_AVAILABLE
270
+ restart_outdated_workers if REFORKING_AVAILABLE
257
271
  end
258
272
 
259
273
  master_sleep(sleep_time) if sleep
260
274
  when :QUIT # graceful shutdown
275
+ logger.info "QUIT received, starting graceful shutdown"
261
276
  return StopIteration
262
277
  when :TERM, :INT # immediate shutdown
278
+ logger.info "#{message} received, starting immediate shutdown"
263
279
  stop(false)
264
280
  return StopIteration
265
281
  when :USR2 # trigger a promotion
@@ -290,6 +306,7 @@ module Pitchfork
290
306
 
291
307
  # Terminates all workers, but does not exit master process
292
308
  def stop(graceful = true)
309
+ wait_for_pending_workers
293
310
  self.listeners = []
294
311
  limit = time_now + timeout
295
312
  until @children.workers.empty? || time_now > limit
@@ -302,6 +319,7 @@ module Pitchfork
302
319
  reap_all_workers
303
320
  end
304
321
  kill_each_child(:KILL)
322
+ @promotion_lock.unlink
305
323
  end
306
324
 
307
325
  def rewindable_input
@@ -333,8 +351,6 @@ module Pitchfork
333
351
 
334
352
  # wait for a signal handler to wake us up and then consume the pipe
335
353
  def master_sleep(sec)
336
- sec = MAX_SLEEP if sec > MAX_SLEEP
337
-
338
354
  @control_socket[0].wait(sec) or return
339
355
  case message = @control_socket[0].recvmsg_nonblock(exception: false)
340
356
  when :wait_readable, NOOP
@@ -351,7 +367,7 @@ module Pitchfork
351
367
 
352
368
  # reaps all unreaped workers
353
369
  def reap_all_workers
354
- begin
370
+ loop do
355
371
  wpid, status = Process.waitpid2(-1, Process::WNOHANG)
356
372
  wpid or return
357
373
  worker = @children.reap(wpid) and worker.close rescue nil
@@ -362,7 +378,7 @@ module Pitchfork
362
378
  end
363
379
  rescue Errno::ECHILD
364
380
  break
365
- end while true
381
+ end
366
382
  end
367
383
 
368
384
  def listener_sockets
@@ -374,15 +390,6 @@ module Pitchfork
374
390
  listener_fds
375
391
  end
376
392
 
377
- def close_sockets_on_exec(sockets)
378
- (3..1024).each do |io|
379
- next if sockets.include?(io)
380
- io = IO.for_fd(io) rescue next
381
- io.autoclose = false
382
- io.close_on_exec = true
383
- end
384
- end
385
-
386
393
  # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
387
394
  def murder_lazy_workers
388
395
  next_sleep = @timeout - 1
@@ -413,17 +420,17 @@ module Pitchfork
413
420
  end
414
421
 
415
422
  unless @children.pending_promotion?
416
- @children.refresh
417
- if new_mold = @mold_selector.call(self)
423
+ if new_mold = @children.fresh_workers.first
418
424
  @children.promote(new_mold)
419
425
  else
420
- logger.error("The mold select didn't return a candidate")
426
+ logger.error("No children at all???")
421
427
  end
422
428
  else
423
429
  end
424
430
  end
425
431
 
426
432
  def after_fork_internal
433
+ @promotion_lock.at_fork
427
434
  @control_socket[0].close_write # this is master-only, now
428
435
  @ready_pipe.close if @ready_pipe
429
436
  Pitchfork::Configurator::RACKUP.clear
@@ -435,9 +442,9 @@ module Pitchfork
435
442
  end
436
443
 
437
444
  def spawn_worker(worker, detach:)
438
- before_fork.call(self, worker)
445
+ logger.info("worker=#{worker.nr} gen=#{worker.generation} spawning...")
439
446
 
440
- pid = fork do
447
+ pid = Pitchfork.clean_fork do
441
448
  # We double fork so that the new worker is re-attached back
442
449
  # to the master.
443
450
  # This requires either PR_SET_CHILD_SUBREAPER which is exclusive to Linux 3.4
@@ -467,10 +474,11 @@ module Pitchfork
467
474
  def spawn_initial_mold
468
475
  mold = Worker.new(nil)
469
476
  mold.create_socketpair!
470
- mold.pid = fork do
471
- after_fork_internal
477
+ mold.pid = Pitchfork.clean_fork do
478
+ @promotion_lock.try_lock
472
479
  mold.after_fork_in_child
473
480
  build_app!
481
+ bind_listeners!
474
482
  mold_loop(mold)
475
483
  end
476
484
  @children.register_mold(mold)
@@ -484,9 +492,11 @@ module Pitchfork
484
492
  end
485
493
  worker = Pitchfork::Worker.new(worker_nr)
486
494
 
487
- if !@children.mold || !@children.mold.spawn_worker(worker)
488
- # If there's no mold, or the mold was somehow unreachable
489
- # we fallback to spawning the missing workers ourselves.
495
+ if REFORKING_AVAILABLE
496
+ unless @children.mold&.spawn_worker(worker)
497
+ @logger.error("Failed to send a spawn_woker command")
498
+ end
499
+ else
490
500
  spawn_worker(worker, detach: false)
491
501
  end
492
502
  # We could directly register workers when we spawn from the
@@ -504,7 +514,7 @@ module Pitchfork
504
514
  while @children.pending_workers?
505
515
  master_sleep(0.5)
506
516
  if monitor_loop(false) == StopIteration
507
- break
517
+ return StopIteration
508
518
  end
509
519
  end
510
520
  end
@@ -515,34 +525,21 @@ module Pitchfork
515
525
  @children.each_worker { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
516
526
  end
517
527
 
518
- def automatically_refork_workers
528
+ def restart_outdated_workers
519
529
  # If we're already in the middle of forking a new generation, we just continue
520
- if @children.mold
521
- # We don't shutdown any outdated worker if any worker is already being spawned
522
- # or a worker is exiting. Workers are only reforked one by one to minimize the
523
- # impact on capacity.
524
- # In the future we may want to use a dynamic limit, e.g. 10% of workers may be down at
525
- # a time.
526
- return if @children.pending_workers?
527
- return if @children.workers.any?(&:exiting?)
528
-
529
- if outdated_worker = @children.workers.find { |w| w.generation < @children.mold.generation }
530
- logger.info("worker=#{outdated_worker.nr} pid=#{outdated_worker.pid} restarting")
531
- outdated_worker.soft_kill(:QUIT)
532
- return # That's all folks
533
- end
534
- end
530
+ return unless @children.mold
535
531
 
536
- # If all workers are alive and well, we can consider reforking a new generation
537
- if @refork_condition
538
- @children.refresh
539
- if @refork_condition.met?(@children, logger)
540
- logger.info("Refork condition met, scheduling a promotion")
541
- unless @sig_queue.include?(:USR2)
542
- @sig_queue << :USR2
543
- awaken_master
544
- end
545
- end
532
+ # We don't shutdown any outdated worker if any worker is already being spawned
533
+ # or a worker is exiting. Workers are only reforked one by one to minimize the
534
+ # impact on capacity.
535
+ # In the future we may want to use a dynamic limit, e.g. 10% of workers may be down at
536
+ # a time.
537
+ return if @children.pending_workers?
538
+ return if @children.workers.any?(&:exiting?)
539
+
540
+ if outdated_worker = @children.workers.find { |w| w.generation < @children.mold.generation }
541
+ logger.info("worker=#{outdated_worker.nr} pid=#{outdated_worker.pid} restarting")
542
+ outdated_worker.soft_kill(:QUIT)
546
543
  end
547
544
  end
548
545
 
@@ -572,22 +569,11 @@ module Pitchfork
572
569
  end
573
570
 
574
571
  def e103_response_write(client, headers)
575
- response = if @request.response_start_sent
576
- "103 Early Hints\r\n"
577
- else
578
- "HTTP/1.1 103 Early Hints\r\n"
579
- end
580
-
581
- headers.each_pair do |k, vs|
582
- next if !vs || vs.empty?
583
- values = vs.to_s.split("\n".freeze)
584
- values.each do |v|
585
- response << "#{k}: #{v}\r\n"
586
- end
587
- end
588
- response << "\r\n".freeze
589
- response << "HTTP/1.1 ".freeze if @request.response_start_sent
590
- client.write(response)
572
+ rss = @request.response_start_sent
573
+ buf = rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n"
574
+ headers.each { |key, value| append_header(buf, key, value) }
575
+ buf << (rss ? "\r\nHTTP/1.1 ".freeze : "\r\n".freeze)
576
+ client.write(buf)
591
577
  end
592
578
 
593
579
  def e100_response_write(client, env)
@@ -678,6 +664,7 @@ module Pitchfork
678
664
 
679
665
  def init_mold_process(worker)
680
666
  proc_name "mold (gen: #{worker.generation})"
667
+ after_promotion.call(self, worker)
681
668
  readers = [worker]
682
669
  trap(:QUIT) { nuke_listeners!(readers) }
683
670
  readers
@@ -713,6 +700,12 @@ module Pitchfork
713
700
  client = false if client == :wait_readable
714
701
  if client
715
702
  case client
703
+ when Message::PromoteWorker
704
+ if @promotion_lock.try_lock
705
+ logger.info("Refork asked by master, promoting ourselves")
706
+ worker.tick = time_now.to_i
707
+ return worker.promoted!
708
+ end
716
709
  when Message
717
710
  worker.update(client)
718
711
  else
@@ -721,11 +714,21 @@ module Pitchfork
721
714
  end
722
715
  worker.tick = time_now.to_i
723
716
  end
724
- return if worker.mold? # We've been promoted we can exit the loop
725
717
  end
726
718
 
727
719
  # timeout so we can .tick and keep parent from SIGKILL-ing us
728
720
  worker.tick = time_now.to_i
721
+ if @refork_condition && !worker.outdated?
722
+ if @refork_condition.met?(worker, logger)
723
+ if @promotion_lock.try_lock
724
+ logger.info("Refork condition met, promoting ourselves")
725
+ return worker.promote! # We've been promoted we can exit the loop
726
+ else
727
+ # TODO: if we couldn't acquire the lock, we should backoff the refork_condition to avoid hammering the lock
728
+ end
729
+ end
730
+ end
731
+
729
732
  waiter.get_readers(ready, readers, @timeout * 500) # to milliseconds, but halved
730
733
  rescue => e
731
734
  Pitchfork.log_error(@logger, "listen loop error", e) if readers[0]
@@ -735,10 +738,9 @@ module Pitchfork
735
738
  def mold_loop(mold)
736
739
  readers = init_mold_process(mold)
737
740
  waiter = prep_readers(readers)
738
- mold.acknowlege_promotion(@control_socket[1])
739
-
741
+ mold.declare_promotion(@control_socket[1])
742
+ @promotion_lock.unlock
740
743
  ready = readers.dup
741
- # TODO: mold ready callback?
742
744
 
743
745
  begin
744
746
  mold.tick = time_now.to_i
@@ -750,7 +752,11 @@ module Pitchfork
750
752
  when false
751
753
  # no message, keep looping
752
754
  when Message::SpawnWorker
753
- spawn_worker(Worker.new(message.nr, generation: mold.generation), detach: true)
755
+ begin
756
+ spawn_worker(Worker.new(message.nr, generation: mold.generation), detach: true)
757
+ rescue => error
758
+ raise BootFailure, error.message
759
+ end
754
760
  else
755
761
  logger.error("Unexpected mold message #{message.inspect}")
756
762
  end
@@ -760,7 +766,7 @@ module Pitchfork
760
766
  mold.tick = time_now.to_i
761
767
  waiter.get_readers(ready, readers, @timeout * 500) # to milliseconds, but halved
762
768
  rescue => e
763
- Pitchfork.log_error(@logger, "listen loop error", e) if readers[0]
769
+ Pitchfork.log_error(@logger, "mold loop error", e) if readers[0]
764
770
  end while readers[0]
765
771
  end
766
772
 
@@ -7,9 +7,9 @@ module Pitchfork
7
7
  @limits = request_counts
8
8
  end
9
9
 
10
- def met?(children, logger)
11
- if limit = @limits[children.last_generation]
12
- if worker = children.fresh_workers.find { |w| w.requests_count >= limit }
10
+ def met?(worker, logger)
11
+ if limit = @limits.fetch(worker.generation) { @limits.last }
12
+ if worker.requests_count >= limit
13
13
  logger.info("worker=#{worker.nr} pid=#{worker.pid} processed #{worker.requests_count} requests, triggering a refork")
14
14
  return true
15
15
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pitchfork
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  module Const
6
6
  UNICORN_VERSION = '6.1.0'
7
7
  end
@@ -7,14 +7,13 @@ module Pitchfork
7
7
  # releases of pitchfork. Knowledge of this class is generally not
8
8
  # not needed for most users of pitchfork.
9
9
  #
10
- # Some users may want to access it in the before_fork/after_fork hooks.
10
+ # Some users may want to access it in the after_promotion/after_fork hooks.
11
11
  # See the Pitchfork::Configurator RDoc for examples.
12
12
  class Worker
13
13
  # :stopdoc:
14
14
  EXIT_SIGNALS = [:QUIT, :TERM]
15
- @generation = 0
16
15
  attr_accessor :nr, :pid, :generation
17
- attr_reader :master
16
+ attr_reader :master, :requests_count
18
17
 
19
18
  def initialize(nr, pid: nil, generation: 0)
20
19
  @nr = nr
@@ -23,6 +22,7 @@ module Pitchfork
23
22
  @mold = false
24
23
  @to_io = @master = nil
25
24
  @exiting = false
25
+ @requests_count = 0
26
26
  if nr
27
27
  build_raindrops(nr)
28
28
  else
@@ -42,13 +42,17 @@ module Pitchfork
42
42
  @exiting
43
43
  end
44
44
 
45
+ def outdated?
46
+ CURRENT_GENERATION_DROP[0] > @generation
47
+ end
48
+
45
49
  def update(message)
46
50
  message.class.members.each do |member|
47
51
  send("#{member}=", message.public_send(member))
48
52
  end
49
53
 
50
54
  case message
51
- when Message::WorkerPromoted, Message::PromoteWorker
55
+ when Message::WorkerPromoted
52
56
  promoted!
53
57
  end
54
58
  end
@@ -60,9 +64,10 @@ module Pitchfork
60
64
  @master.close
61
65
  end
62
66
 
63
- def acknowlege_promotion(control_socket)
67
+ def declare_promotion(control_socket)
64
68
  message = Message::WorkerPromoted.new(@nr, Process.pid, generation)
65
69
  control_socket.sendmsg(message)
70
+ CURRENT_GENERATION_DROP[0] = @generation
66
71
  end
67
72
 
68
73
  def promote(generation)
@@ -73,11 +78,17 @@ module Pitchfork
73
78
  send_message_nonblock(Message::SpawnWorker.new(new_worker.nr))
74
79
  end
75
80
 
81
+ def promote!
82
+ @generation += 1
83
+ promoted!
84
+ end
85
+
76
86
  def promoted!
77
87
  @mold = true
78
88
  @nr = nil
79
89
  @drop_offset = 0
80
90
  @tick_drop = MOLD_DROP
91
+ self
81
92
  end
82
93
 
83
94
  def mold?
@@ -169,15 +180,11 @@ module Pitchfork
169
180
  end
170
181
 
171
182
  def reset
172
- @requests_drop[@drop_offset] = 0
173
- end
174
-
175
- def requests_count
176
- @requests_drop[@drop_offset]
183
+ @requests_count = 0
177
184
  end
178
185
 
179
- def increment_requests_count
180
- @requests_drop.incr(@drop_offset)
186
+ def increment_requests_count(by = 1)
187
+ @requests_count += by
181
188
  end
182
189
 
183
190
  # called in both the master (reaping worker) and worker (SIGQUIT handler)
@@ -215,9 +222,9 @@ module Pitchfork
215
222
  end
216
223
 
217
224
  MOLD_DROP = Raindrops.new(1)
225
+ CURRENT_GENERATION_DROP = Raindrops.new(1)
218
226
  PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
219
227
  TICK_DROPS = []
220
- REQUEST_DROPS = []
221
228
 
222
229
  class << self
223
230
  # Since workers are created from another process, we have to
@@ -228,7 +235,6 @@ module Pitchfork
228
235
  def preallocate_drops(workers_count)
229
236
  0.upto(workers_count / PER_DROP) do |i|
230
237
  TICK_DROPS[i] = Raindrops.new(PER_DROP)
231
- REQUEST_DROPS[i] = Raindrops.new(PER_DROP)
232
238
  end
233
239
  end
234
240
  end
@@ -237,8 +243,7 @@ module Pitchfork
237
243
  drop_index = drop_nr / PER_DROP
238
244
  @drop_offset = drop_nr % PER_DROP
239
245
  @tick_drop = TICK_DROPS[drop_index] ||= Raindrops.new(PER_DROP)
240
- @requests_drop = REQUEST_DROPS[drop_index] ||= Raindrops.new(PER_DROP)
241
- @tick_drop[@drop_offset] = @requests_drop[@drop_offset] = 0
246
+ @tick_drop[@drop_offset] = 0
242
247
  end
243
248
  end
244
249
  end
data/lib/pitchfork.rb CHANGED
@@ -57,40 +57,15 @@ module Pitchfork
57
57
  Object.const_get(File.basename(ru, '.rb').capitalize)
58
58
  end
59
59
 
60
- if $DEBUG
61
- require 'pp'
62
- pp({ :inner_app => inner_app })
63
- end
64
-
65
- return inner_app unless server.default_middleware
66
-
67
- middleware = { # order matters
68
- ContentLength: nil,
69
- Chunked: nil,
70
- CommonLogger: [ $stderr ],
71
- ShowExceptions: nil,
72
- Lint: nil,
73
- TempfileReaper: nil,
74
- }
75
-
76
- # return value, matches rackup defaults based on env
77
- # Pitchfork does not support persistent connections, but Rainbows!
78
- # and Zbatery both do. Users accustomed to the Rack::Server default
79
- # middlewares will need ContentLength/Chunked middlewares.
80
60
  case ENV["RACK_ENV"]
81
61
  when "development"
82
- when "deployment"
83
- middleware.delete(:ShowExceptions)
84
- middleware.delete(:Lint)
62
+ Rack::Builder.new do
63
+ use(Rack::Lint)
64
+ run inner_app
65
+ end.to_app
85
66
  else
86
- return inner_app
67
+ inner_app
87
68
  end
88
- Rack::Builder.new do
89
- middleware.each do |m, args|
90
- use(Rack.const_get(m), *args) if Rack.const_defined?(m)
91
- end
92
- run inner_app
93
- end.to_app
94
69
  end
95
70
  end
96
71
 
@@ -146,13 +121,32 @@ module Pitchfork
146
121
  raise
147
122
  end
148
123
  end
124
+
125
+ def self.clean_fork(&block)
126
+ # We fork from a thread to start with a clean stack.
127
+ # If we didn't the base stack would grow after each refork
128
+ # putting an effective limit on the number of generations.
129
+ parent_thread = Thread.current
130
+ Thread.new do
131
+ current_thread = Thread.current
132
+ # We copy over any thread state it might have
133
+ parent_thread.keys.each do |key|
134
+ current_thread[key] = parent_thread[key]
135
+ end
136
+ parent_thread.thread_variables.each do |variable|
137
+ current_thread.thread_variable_set(variable, parent_thread.thread_variable_get(variable))
138
+ end
139
+
140
+ fork(&block)
141
+ end.value
142
+ end
149
143
  # :startdoc:
150
144
  end
151
145
  # :enddoc:
152
146
 
153
147
  %w(
154
148
  const socket_helper stream_input tee_input mem_info children message http_parser
155
- refork_condition mold_selector configurator tmpio http_response worker http_server
149
+ refork_condition configurator tmpio http_response worker http_server
156
150
  ).each do |s|
157
151
  require_relative "pitchfork/#{s}"
158
152
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pitchfork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-05 00:00:00.000000000 Z
11
+ date: 2023-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raindrops
@@ -96,13 +96,13 @@ files:
96
96
  - lib/pitchfork/children.rb
97
97
  - lib/pitchfork/configurator.rb
98
98
  - lib/pitchfork/const.rb
99
+ - lib/pitchfork/flock.rb
99
100
  - lib/pitchfork/http_parser.rb
100
101
  - lib/pitchfork/http_response.rb
101
102
  - lib/pitchfork/http_server.rb
102
103
  - lib/pitchfork/launcher.rb
103
104
  - lib/pitchfork/mem_info.rb
104
105
  - lib/pitchfork/message.rb
105
- - lib/pitchfork/mold_selector.rb
106
106
  - lib/pitchfork/preread_input.rb
107
107
  - lib/pitchfork/refork_condition.rb
108
108
  - lib/pitchfork/select_waiter.rb
@@ -133,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubygems_version: 3.3.7
136
+ rubygems_version: 3.4.6
137
137
  signing_key:
138
138
  specification_version: 4
139
139
  summary: Rack HTTP server for fast clients and Unix
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Pitchfork
4
- module MoldSelector
5
- class LeastSharedMemory
6
- def call(server)
7
- workers = server.children.fresh_workers
8
- if workers.empty?
9
- server.logger.info("No current generation workers yet")
10
- return
11
- end
12
- candidate = workers.shift
13
-
14
- workers.each do |worker|
15
- if worker.meminfo.shared_memory < candidate.meminfo.shared_memory
16
- # We suppose that a worker with a lower amount of shared memory
17
- # has warmed up more caches & such, hence is closer to stabilize
18
- # making it a better candidate.
19
- candidate = worker
20
- end
21
- end
22
- parent_meminfo = server.children.mold&.meminfo || MemInfo.new(Process.pid)
23
- cow_efficiency = candidate.meminfo.cow_efficiency(parent_meminfo)
24
- server.logger.info("worker=#{candidate.nr} pid=#{candidate.pid} selected as new mold shared_memory_kb=#{candidate.meminfo.shared_memory} cow=#{cow_efficiency.round(1)}%")
25
- candidate
26
- end
27
- end
28
- end
29
- end