pitchfork 0.1.1 → 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.

Potentially problematic release.


This version of pitchfork might be problematic. Click here for more details.

@@ -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