zapp 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/Gemfile.lock +1 -1
  4. data/examples/config/app.rb +5 -0
  5. data/examples/config/puma.rb +10 -0
  6. data/examples/config/zapp.rb +13 -0
  7. data/lib/zapp/configuration.rb +9 -1
  8. data/lib/zapp/http_context/request.rb +2 -2
  9. data/lib/zapp/logger/base.rb +103 -0
  10. data/lib/zapp/logger.rb +20 -57
  11. data/lib/zapp/server.rb +6 -4
  12. data/lib/zapp/socket_pipe/receiver.rb +0 -8
  13. data/lib/zapp/version.rb +1 -1
  14. data/lib/zapp/worker/request_processor.rb +27 -32
  15. data/lib/zapp/worker.rb +5 -6
  16. data/lib/zapp/worker_pool.rb +6 -5
  17. data/lib/zapp.rb +5 -2
  18. metadata +6 -96
  19. data/examples/rails-app/.browserslistrc +0 -1
  20. data/examples/rails-app/.gitattributes +0 -10
  21. data/examples/rails-app/.gitignore +0 -40
  22. data/examples/rails-app/.ruby-version +0 -1
  23. data/examples/rails-app/Gemfile +0 -58
  24. data/examples/rails-app/Gemfile.lock +0 -255
  25. data/examples/rails-app/Rakefile +0 -8
  26. data/examples/rails-app/app/assets/config/manifest.js +0 -2
  27. data/examples/rails-app/app/assets/images/.keep +0 -0
  28. data/examples/rails-app/app/assets/stylesheets/application.css +0 -15
  29. data/examples/rails-app/app/channels/application_cable/channel.rb +0 -6
  30. data/examples/rails-app/app/channels/application_cable/connection.rb +0 -6
  31. data/examples/rails-app/app/controllers/application_controller.rb +0 -4
  32. data/examples/rails-app/app/controllers/concerns/.keep +0 -0
  33. data/examples/rails-app/app/helpers/application_helper.rb +0 -4
  34. data/examples/rails-app/app/javascript/channels/consumer.js +0 -6
  35. data/examples/rails-app/app/javascript/channels/index.js +0 -5
  36. data/examples/rails-app/app/javascript/packs/application.js +0 -13
  37. data/examples/rails-app/app/jobs/application_job.rb +0 -9
  38. data/examples/rails-app/app/mailers/application_mailer.rb +0 -6
  39. data/examples/rails-app/app/models/application_record.rb +0 -5
  40. data/examples/rails-app/app/models/concerns/.keep +0 -0
  41. data/examples/rails-app/app/views/layouts/application.html.erb +0 -16
  42. data/examples/rails-app/app/views/layouts/mailer.html.erb +0 -13
  43. data/examples/rails-app/app/views/layouts/mailer.text.erb +0 -1
  44. data/examples/rails-app/babel.config.js +0 -82
  45. data/examples/rails-app/bin/bundle +0 -118
  46. data/examples/rails-app/bin/rails +0 -7
  47. data/examples/rails-app/bin/rake +0 -7
  48. data/examples/rails-app/bin/setup +0 -38
  49. data/examples/rails-app/bin/spring +0 -16
  50. data/examples/rails-app/bin/webpack +0 -21
  51. data/examples/rails-app/bin/webpack-dev-server +0 -21
  52. data/examples/rails-app/bin/yarn +0 -19
  53. data/examples/rails-app/bin/zapp +0 -1
  54. data/examples/rails-app/config/application.rb +0 -24
  55. data/examples/rails-app/config/boot.rb +0 -6
  56. data/examples/rails-app/config/cable.yml +0 -10
  57. data/examples/rails-app/config/credentials.yml.enc +0 -1
  58. data/examples/rails-app/config/database.yml +0 -25
  59. data/examples/rails-app/config/environment.rb +0 -7
  60. data/examples/rails-app/config/environments/development.rb +0 -78
  61. data/examples/rails-app/config/environments/production.rb +0 -122
  62. data/examples/rails-app/config/environments/test.rb +0 -62
  63. data/examples/rails-app/config/initializers/application_controller_renderer.rb +0 -9
  64. data/examples/rails-app/config/initializers/assets.rb +0 -16
  65. data/examples/rails-app/config/initializers/backtrace_silencers.rb +0 -10
  66. data/examples/rails-app/config/initializers/content_security_policy.rb +0 -31
  67. data/examples/rails-app/config/initializers/cookies_serializer.rb +0 -7
  68. data/examples/rails-app/config/initializers/filter_parameter_logging.rb +0 -8
  69. data/examples/rails-app/config/initializers/inflections.rb +0 -17
  70. data/examples/rails-app/config/initializers/mime_types.rb +0 -5
  71. data/examples/rails-app/config/initializers/permissions_policy.rb +0 -12
  72. data/examples/rails-app/config/initializers/wrap_parameters.rb +0 -16
  73. data/examples/rails-app/config/locales/en.yml +0 -33
  74. data/examples/rails-app/config/puma.rb +0 -45
  75. data/examples/rails-app/config/routes.rb +0 -5
  76. data/examples/rails-app/config/spring.rb +0 -8
  77. data/examples/rails-app/config/storage.yml +0 -34
  78. data/examples/rails-app/config/webpack/development.js +0 -5
  79. data/examples/rails-app/config/webpack/environment.js +0 -3
  80. data/examples/rails-app/config/webpack/production.js +0 -5
  81. data/examples/rails-app/config/webpack/test.js +0 -5
  82. data/examples/rails-app/config/webpacker.yml +0 -92
  83. data/examples/rails-app/config/zapp.rb +0 -10
  84. data/examples/rails-app/config.ru +0 -7
  85. data/examples/rails-app/db/seeds.rb +0 -8
  86. data/examples/rails-app/lib/assets/.keep +0 -0
  87. data/examples/rails-app/lib/tasks/.keep +0 -0
  88. data/examples/rails-app/log/.keep +0 -0
  89. data/examples/rails-app/package.json +0 -17
  90. data/examples/rails-app/postcss.config.js +0 -12
  91. data/examples/rails-app/public/404.html +0 -67
  92. data/examples/rails-app/public/422.html +0 -67
  93. data/examples/rails-app/public/500.html +0 -66
  94. data/examples/rails-app/public/apple-touch-icon-precomposed.png +0 -0
  95. data/examples/rails-app/public/apple-touch-icon.png +0 -0
  96. data/examples/rails-app/public/favicon.ico +0 -0
  97. data/examples/rails-app/public/robots.txt +0 -1
  98. data/examples/rails-app/storage/.keep +0 -0
  99. data/examples/rails-app/test/application_system_test_case.rb +0 -7
  100. data/examples/rails-app/test/channels/application_cable/connection_test.rb +0 -15
  101. data/examples/rails-app/test/controllers/.keep +0 -0
  102. data/examples/rails-app/test/fixtures/files/.keep +0 -0
  103. data/examples/rails-app/test/helpers/.keep +0 -0
  104. data/examples/rails-app/test/integration/.keep +0 -0
  105. data/examples/rails-app/test/mailers/.keep +0 -0
  106. data/examples/rails-app/test/models/.keep +0 -0
  107. data/examples/rails-app/test/system/.keep +0 -0
  108. data/examples/rails-app/test/test_helper.rb +0 -17
  109. data/examples/rails-app/tmp/.keep +0 -0
  110. data/examples/rails-app/tmp/pids/.keep +0 -0
  111. data/examples/rails-app/vendor/.keep +0 -0
  112. data/examples/rails-app/yarn.lock +0 -6973
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '083c3deaea0cc22f0bd269afbccdd556bf66743771aca04f63f125809c934473'
4
- data.tar.gz: cd3a771ca1d0e898ddaa99c71b741412e4be8370286ec484b2059317cb632802
3
+ metadata.gz: 6b4a4b6bdf9ac52a419e899e41344faf5ff15fdde704f70e93fe71cee334e70e
4
+ data.tar.gz: 4bd738d7ca1919ceef29612c5bf9a07a26ad8bda944b9adfc52197435dbb1a0a
5
5
  SHA512:
6
- metadata.gz: 227f3dc9ce4c2cac931c89f2a3bca81f90f91031a78998258befb02515ee5eb1af386741646bdecc14e82649cc5cc876f360cbfb8a01bf84e119b062ab3a0eea
7
- data.tar.gz: ffa29c579b1621eca0093e2c3d9a4f02975aa3be6a81aa17348aff7a7f5f54cecf4ea8aeb7829c6a821d4691c2ac9a2cd2d0be33204f2a02b1fd163b5c6990e3
6
+ metadata.gz: 26554e64959a88e35bf1b7f9657909da61de5a92c980550df415ae0b5564f899b25922e1fbb69d79a39a72506fe903654a007555c8e85ce3b437eb1cbbf8ccb3
7
+ data.tar.gz: c790f81f5fa7d1867ee6f33c3fff92f3df44ece8051e9dc3f9ed260724dd0236a3d3fd1814ba3dcf54f3743713782445a5001f54fcb275872943665055593b64
data/.rubocop.yml CHANGED
@@ -5,13 +5,16 @@ AllCops:
5
5
  Exclude:
6
6
  - examples/**/*
7
7
 
8
+ Gemspec/RequireMFA:
9
+ Enabled: false
10
+
8
11
  Style/DocumentationMethod:
9
12
  Enabled: false
10
13
  Style/MissingElse:
11
14
  Enabled: false
12
15
  Style/StringLiterals:
13
16
  EnforcedStyle: double_quotes
14
- Style/InlineComments:
17
+ Style/InlineComment:
15
18
  Enabled: false
16
19
  Style/Copyright:
17
20
  Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- zapp (0.1.1)
4
+ zapp (0.2.1)
5
5
  concurrent-ruby (~> 1.1.9)
6
6
  puma (~> 5.5.2)
7
7
  rack (~> 2.2.3)
@@ -0,0 +1,5 @@
1
+ class App
2
+ def self.call(env)
3
+ [200, {}, ["Hello from Zapp", env.to_s]]
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require_relative("app")
2
+
3
+ max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
4
+ min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
5
+ threads(min_threads_count, max_threads_count)
6
+ worker_timeout(3600) if ENV.fetch("RAILS_ENV", "development") == "development"
7
+ port(ENV.fetch("PORT", 3000))
8
+ environment(ENV.fetch("RAILS_ENV", "development"))
9
+ workers ENV.fetch("WEB_CONCURRENCY") { 2 }
10
+ app(App)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("json")
4
+
5
+ class App
6
+ def self.call(env)
7
+ [200, {}, ["Hello from Zapp", JSON.generate(env)]]
8
+ end
9
+ end
10
+
11
+ parallelism(4)
12
+ threads_per_worker(25)
13
+ app(App)
@@ -12,6 +12,7 @@ module Zapp
12
12
  :parallelism,
13
13
  :threads_per_worker,
14
14
  :logger_class,
15
+ :logger_out_io,
15
16
  :log_requests,
16
17
  :log_uncaught_errors,
17
18
  :host,
@@ -33,6 +34,7 @@ module Zapp
33
34
 
34
35
  # Default logging behavior
35
36
  logger_class: Zapp::Logger,
37
+ logger_out_io: $stdout,
36
38
  log_requests: true,
37
39
  log_uncaught_errors: true,
38
40
 
@@ -72,7 +74,7 @@ module Zapp
72
74
  @app = new unless new.nil?
73
75
 
74
76
  @app ||= begin
75
- raise(Zapp::ZapError, "Missing rackup file '#{rackup_file}'") unless File.exist?(rackup_file)
77
+ raise(Zapp::ZappError, "Missing rackup file '#{rackup_file}'") unless File.exist?(rackup_file)
76
78
 
77
79
  rack_app, = rack_builder.parse_file(rackup_file)
78
80
 
@@ -98,6 +100,12 @@ module Zapp
98
100
  @logger_class = new
99
101
  end
100
102
 
103
+ def logger_out_io(new = nil)
104
+ return @logger_out_io if new.nil?
105
+
106
+ @logger_out_io = new
107
+ end
108
+
101
109
  def log_requests(new = nil)
102
110
  return @log_requests if new.nil?
103
111
 
@@ -9,7 +9,7 @@ module Zapp
9
9
  attr_reader(:raw, :data, :body)
10
10
 
11
11
  # Request parsing is done threaded, but not in separate Ractors.
12
- # So we allocate an HTTP parser per thread and assigns it to this hash key on Thread.current
12
+ # So we allocate an HTTP parser per thread and assign it to this hash key in Thread.current
13
13
  PARSER_THREAD_HASH_KEY = "PUMA_PARSER_INSTANCE"
14
14
 
15
15
  def initialize(socket:)
@@ -19,7 +19,7 @@ module Zapp
19
19
 
20
20
  parser.execute(data, raw, 0)
21
21
 
22
- @body = Zapp::InputStream.new(string: "parser.body")
22
+ @body = Zapp::InputStream.new(string: parser.body)
23
23
 
24
24
  parser.reset
25
25
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zapp
4
+ class Logger
5
+ # Base contains all the logging functionality and is included both as class and instance methods of Zap::Logger
6
+ # This allows logging without creating new instances,
7
+ # while allowing Ractors to create their own instances for thread safety
8
+ module Base
9
+ attr_writer(:level, :prefix)
10
+
11
+ LEVELS = { TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4 }.freeze
12
+
13
+ FROZEN_ENV = ENV.map { |k, v| [k.freeze, v.freeze] }
14
+ .to_h.freeze
15
+
16
+ # The hash key in Ractor.current that stores the mutex for writing to output
17
+ OUT_IO_MUTEX_KEY = "ZAPP_LOGGER_OUT_IO_MUTEX"
18
+
19
+ def trace(msg) = log("TRACE", msg)
20
+
21
+ def debug(msg) = log("DEBUG", msg)
22
+
23
+ def info(msg) = log("INFO", msg)
24
+
25
+ def warn(msg) = log("WARN", msg)
26
+
27
+ def error(msg) = log("ERROR", msg)
28
+
29
+ def level
30
+ @level ||= begin
31
+ log_level = FROZEN_ENV["LOG_LEVEL"]
32
+
33
+ if log_level == "" || log_level.nil?
34
+ LEVELS[:DEBUG]
35
+ else
36
+ resolved_level = LEVELS[log_level.upcase.to_sym]
37
+
38
+ if resolved_level.nil?
39
+ raise(
40
+ Zapp::ZappError,
41
+ "Invalid log level '#{log_level.upcase}', must be one of [#{LEVELS.keys.join(', ')}]"
42
+ )
43
+ end
44
+
45
+ resolved_level
46
+ end
47
+ end
48
+ end
49
+
50
+ def log(current_level, msg, **_tags)
51
+ return unless level <= LEVELS[current_level.to_sym]
52
+
53
+ write("--- #{prefix} [#{current_level}] #{msg}\n")
54
+ end
55
+
56
+ def flush
57
+ writing_thread_pool.wait_for_termination(0.1)
58
+
59
+ out_io_mutex.synchronize do
60
+ out.flush
61
+ end
62
+ end
63
+
64
+ # @param new_out [IO]
65
+ def out=(new_out)
66
+ @out = new_out
67
+ end
68
+
69
+ protected
70
+
71
+ # @return [IO]
72
+ def out
73
+ @out ||= Zapp.config.logger_out_io
74
+ end
75
+
76
+ # @return [String]
77
+ def prefix = @prefix ||= Ractor.current.name
78
+
79
+ def write(msg)
80
+ writing_thread_pool.post do
81
+ out_io_mutex.synchronize do
82
+ out.print(msg)
83
+ end
84
+ end
85
+ end
86
+
87
+ # We really just use this as a queue
88
+ # TODO: There's probably a smarter way of doing this with less overhead,
89
+ # TODO: or maybe we should just actually write logs multi-threaded
90
+ def writing_thread_pool
91
+ @writing_thread_pool ||= Concurrent::ThreadPoolExecutor.new(
92
+ min_threads: 1,
93
+ max_threads: 1,
94
+ max_queue: 100
95
+ )
96
+ end
97
+
98
+ def out_io_mutex
99
+ Ractor.current[OUT_IO_MUTEX_KEY] ||= Thread::Mutex.new
100
+ end
101
+ end
102
+ end
103
+ end
data/lib/zapp/logger.rb CHANGED
@@ -1,74 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative("logger/base")
4
+
3
5
  module Zapp
4
- # The default logger for zap
6
+ # The default logger for Zapp
5
7
  class Logger
6
- # Base contains all the logging functionality and is included both as class and instance methods of Zap::Logger
7
- # This allows logging without creating new instances,
8
- # while allowing Ractors to create their own instances for thread safety
9
- module Base
10
- attr_writer(:level, :prefix)
11
-
12
- LEVELS = { TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4 }.freeze
13
-
14
- FROZEN_ENV = ENV.map { |k, v| [k.freeze, v.freeze] }
15
- .to_h.freeze
16
-
17
- def trace(msg)
18
- log("TRACE", msg)
19
- end
20
-
21
- def debug(msg)
22
- log("DEBUG", msg)
23
- end
8
+ include(Zapp::Logger::Base)
24
9
 
25
- def info(msg)
26
- log("INFO", msg)
27
- end
10
+ def initialize
11
+ yield(self) if block_given?
12
+ end
28
13
 
29
- def warn(msg)
30
- log("WARN", msg)
31
- end
14
+ class << self
15
+ # The hash key in Ractor.current that stores the global Zapp::Logger instance
16
+ GLOBAL_INSTANCE_KEY = "ZAPP_LOGGER_INSTANCE"
32
17
 
33
- def error(msg)
34
- log("ERROR", msg)
18
+ def instance
19
+ Ractor.current[GLOBAL_INSTANCE_KEY] ||= new
35
20
  end
36
21
 
37
- def level
38
- @level ||= begin
39
- log_level = FROZEN_ENV["LOG_LEVEL"]
40
-
41
- if log_level == "" || log_level.nil?
42
- LEVELS[:DEBUG]
43
- else
44
- resolved_level = LEVELS[log_level.upcase.to_sym]
45
-
46
- if resolved_level.nil?
47
- raise(
48
- Zapp::ZappError,
49
- "Invalid log level '#{log_level.upcase}', must be one of [#{LEVELS.keys.join(', ')}]"
50
- )
51
- end
22
+ private
52
23
 
53
- resolved_level
54
- end
24
+ def method_missing(symbol, *args)
25
+ if respond_to_missing?(symbol)
26
+ instance.public_send(symbol, *args)
27
+ else
28
+ super
55
29
  end
56
30
  end
57
31
 
58
- def log(current_level, msg, **_tags)
59
- puts("--- #{@prefix} [#{current_level}] #{msg}") if level <= LEVELS[current_level.to_sym]
32
+ def respond_to_missing?(symbol, include_private = false)
33
+ instance.respond_to?(symbol) || super(symbol, include_private)
60
34
  end
61
35
  end
62
- include(Zapp::Logger::Base)
63
-
64
- def initialize
65
- yield(self) if block_given?
66
- end
67
-
68
- class << self
69
- include(Zapp::Logger::Base)
70
- end
71
36
  end
72
37
  end
73
-
74
- Zapp::Logger.prefix = "Zapp"
data/lib/zapp/server.rb CHANGED
@@ -11,7 +11,7 @@ module Zapp
11
11
 
12
12
  @socket_pipe_receiver = Zapp::SocketPipe::Receiver.new(pipe: @socket_pipe)
13
13
 
14
- @worker_pool = Zapp::WorkerPool.new(app: Zapp.config.app, socket_pipe: @socket_pipe, context_pipe: @context_pipe)
14
+ @worker_pool = Zapp::WorkerPool.new(socket_pipe: @socket_pipe, context_pipe: @context_pipe)
15
15
  end
16
16
 
17
17
  def run
@@ -38,16 +38,17 @@ module Zapp
38
38
  Zapp::Logger.info("Received signal #{err.class.name}") unless err.nil?
39
39
  Zapp::Logger.info("Gracefully shutting down workers, allowing request processing to finish")
40
40
 
41
- socket_pipe_receiver.drain
42
41
  worker_pool.drain
43
42
 
44
43
  Zapp::Logger.info("Done. See you next time!")
44
+ Zapp::Logger.flush
45
45
  end
46
46
 
47
47
  private
48
48
 
49
49
  def log_start
50
- Zapp::Logger.info("
50
+ Zapp::Logger.info(
51
+ "
51
52
  ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
52
53
  ⚡ ███████╗ █████╗ ██████╗ ██████╗ ⚡
53
54
  ⚡ ╚══███╔╝██╔══██╗██╔══██╗██╔══██╗ ⚡
@@ -56,7 +57,8 @@ module Zapp
56
57
  ⚡ ███████╗██║ ██║██║ ██║ ⚡
57
58
  ⚡ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ⚡
58
59
  ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
59
- ")
60
+ "
61
+ )
60
62
  Zapp::Logger.info("Zapp version: #{Zapp::VERSION}")
61
63
  Zapp::Logger.info("Environment: #{Zapp.config.mode}")
62
64
  Zapp::Logger.info("Serving: #{Zapp.config.env[Rack::RACK_URL_SCHEME]}://#{Zapp.config.host}:#{Zapp.config.port}")
@@ -19,14 +19,6 @@ module Zapp
19
19
  def take
20
20
  Ractor.select(pipe, raw_tcp_pipe)[1]
21
21
  end
22
-
23
- def drain
24
- Thread.new do
25
- loop do
26
- take
27
- end
28
- end
29
- end
30
22
  end
31
23
  end
32
24
  end
data/lib/zapp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zapp
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -4,11 +4,9 @@ module Zapp
4
4
  class Worker < Ractor
5
5
  # Processes HTTP requests
6
6
  class RequestProcessor
7
- attr_reader(:app, :config, :socket_pipe_sender, :context_pipe)
7
+ attr_reader(:socket_pipe_sender, :context_pipe)
8
8
 
9
- def initialize(context_pipe:, socket_pipe:, app:, config:)
10
- @app = app
11
- @config = config
9
+ def initialize(context_pipe:, socket_pipe:)
12
10
  @socket_pipe_sender = Zapp::SocketPipe::Sender.new(pipe: socket_pipe)
13
11
  @context_pipe = context_pipe
14
12
  end
@@ -16,26 +14,24 @@ module Zapp
16
14
  def loop
17
15
  while (context = context_pipe.take)
18
16
  if context == Zapp::WorkerPool::SIGNALS[:EXIT]
19
- logger.trace("Received exit signal, shutting down")
20
- thread_pool.shutdown
17
+ Zapp::Logger.trace("Received exit signal, shutting down")
18
+ shutdown
21
19
  break
22
20
  end
23
21
 
22
+ process = lambda {
23
+ process(context: context)
24
+ }
24
25
 
25
- process = lambda {
26
- process(context: context)
27
- }
28
-
29
- if config.log_requests
30
- log_request_time(context: context, &process)
31
- else
32
- process.call
33
- end
34
-
35
- # We send sockets that the client hasn't closed yet,
36
- # back to the main ractor for HTTP request parsing again
37
- socket_pipe_sender.push(context.socket) unless context.client_closed?
26
+ if Zapp.config.log_requests
27
+ log_request_time(context: context, &process)
28
+ else
29
+ process.call
30
+ end
38
31
 
32
+ # We send sockets that the client hasn't closed yet,
33
+ # back to the main ractor for HTTP request parsing again
34
+ socket_pipe_sender.push(context.socket) unless context.client_closed?
39
35
  end
40
36
  end
41
37
 
@@ -43,16 +39,16 @@ module Zapp
43
39
 
44
40
  # Processes an HTTP request
45
41
  def process(context:)
46
- env = prepare_env(data: context.req.data, body: context.req.body, env: config.env.dup)
42
+ env = prepare_env(data: context.req.data, body: context.req.body, env: Zapp.config.env.dup)
47
43
 
48
- status, headers, response_body_stream = @app.call(env)
44
+ status, headers, response_body_stream = Zapp.config.app.call(env)
49
45
 
50
46
  response_body = body_stream_to_string(response_body_stream)
51
47
 
52
48
  context.res.write(data: response_body, status: status, headers: headers)
53
49
  rescue StandardError => e
54
50
  context.res.write(data: "An unexpected error occurred", status: 500, headers: {})
55
- logger.error("#{e}\n\n#{e.backtrace&.join(",\n")}") if config.log_uncaught_errors
51
+ Zapp::Logger.error("#{e}\n\n#{e.backtrace&.join(",\n")}") if Zapp.config.log_uncaught_errors
56
52
  end
57
53
 
58
54
  # Merges HTTP data and body into the env to be passed to the rack app
@@ -79,7 +75,7 @@ module Zapp
79
75
  path = context.req.data["PATH_INFO"]
80
76
  status = context.res.status
81
77
 
82
- logger.info(
78
+ Zapp::Logger.info(
83
79
  "#{method} #{path} - Completed in #{request_time}ms with status #{status}"
84
80
  )
85
81
  end
@@ -96,19 +92,18 @@ module Zapp
96
92
  response_body
97
93
  end
98
94
 
95
+ def shutdown
96
+ Zapp::Logger.flush
97
+ thread_pool.shutdown
98
+ end
99
+
99
100
  def thread_pool
100
101
  @thread_pool ||= Concurrent::ThreadPoolExecutor.new(
101
- min_threads: config.threads_per_worker,
102
- max_threads: config.threads_per_worker,
103
- max_queue: 1000,
102
+ min_threads: Zapp.config.threads_per_worker,
103
+ max_threads: Zapp.config.threads_per_worker,
104
+ max_queue: 1000
104
105
  )
105
106
  end
106
-
107
- def logger
108
- @logger ||= config.logger_class.new do |l|
109
- l.prefix = Ractor.current.name
110
- end
111
- end
112
107
  end
113
108
  end
114
109
  end
data/lib/zapp/worker.rb CHANGED
@@ -6,19 +6,18 @@ module Zapp
6
6
  # One worker processing requests in parallel
7
7
  class Worker < Ractor
8
8
  class << self
9
- def new(context_pipe:, socket_pipe:, app:, index:)
9
+ def new(context_pipe:, socket_pipe:, index:)
10
10
  super(
11
11
  context_pipe,
12
12
  socket_pipe,
13
- app,
14
13
  Zapp.config.dup,
15
14
  name: name(index)
16
- ) do |context_pipe, socket_pipe, app, config|
15
+ ) do |context_pipe, socket_pipe, config|
16
+ Ractor.current[Zapp::RACTOR_CONFIG_KEY] = config
17
+
17
18
  processor = Zapp::Worker::RequestProcessor.new(
18
19
  socket_pipe: socket_pipe,
19
- context_pipe: context_pipe,
20
- app: app,
21
- config: config
20
+ context_pipe: context_pipe
22
21
  )
23
22
 
24
23
  processor.loop
@@ -9,14 +9,13 @@ module Zapp
9
9
  EXIT: :exit
10
10
  }.freeze
11
11
 
12
- def initialize(app:, context_pipe:, socket_pipe:)
12
+ def initialize(context_pipe:, socket_pipe:)
13
13
  @context_pipe = context_pipe
14
14
  @workers = []
15
15
  Zapp.config.parallelism.times do |i|
16
16
  @workers << Worker.new(
17
17
  context_pipe: context_pipe,
18
18
  socket_pipe: socket_pipe,
19
- app: app,
20
19
  index: i
21
20
  )
22
21
  end
@@ -30,9 +29,11 @@ module Zapp
30
29
  # Finishes processing of all requests and shuts down workers
31
30
  def drain
32
31
  Zapp.config.parallelism.times { process(context: SIGNALS[:EXIT]) }
33
- workers.map(&:terminate)
34
- rescue Ractor::ClosedError
35
- # Ractor has already exited
32
+ workers.map do |w|
33
+ w.terminate
34
+ rescue Ractor::ClosedError
35
+ # Ractor has already exited
36
+ end
36
37
  end
37
38
  end
38
39
  end
data/lib/zapp.rb CHANGED
@@ -11,11 +11,14 @@ require("rack")
11
11
  module Zapp
12
12
  class ZappError < StandardError; end
13
13
 
14
+ # The hash key in Ractor.current that stores the global Zapp::Configuration instance
15
+ RACTOR_CONFIG_KEY = "ZAPP_CONFIG"
16
+
14
17
  class << self
15
18
  def config(reset: false)
16
- @config = Zapp::Configuration.new if reset
19
+ Ractor.current[RACTOR_CONFIG_KEY] = Zapp::Configuration.new if reset
17
20
 
18
- @config ||= Zapp::Configuration.new
21
+ Ractor.current[RACTOR_CONFIG_KEY] ||= Zapp::Configuration.new
19
22
  end
20
23
 
21
24
  def configure