zapp 0.2.1 → 0.2.2

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