Giter Club home page Giter Club logo

iodine's Introduction

iodine - Why Settle for a fast HTTP / WebSocket Server with native Pub/Sub?

Gem Build Status Gem Version Inline docs GitHub

Logo

Iodine is a fast concurrent web application server for real-time Ruby applications, with native support for WebSockets and Pub/Sub services - but it's also so much more.

Iodine is a Ruby wrapper for much of the facil.io C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, leaving you free to concentrate on your application logic.

Iodine includes native support for:

  • HTTP, WebSockets and EventSource (SSE) Services (server);
  • WebSocket connections (server / client);
  • Pub/Sub (with optional Redis Pub/Sub scaling);
  • Fast(!) builtin Mustache templating;
  • Static file service (with automatic gzip support for pre-compressed assets);
  • Optimized Logging to stderr;
  • Asynchronous event scheduling and timers;
  • HTTP/1.1 keep-alive and pipelining;
  • Heap Fragmentation Protection;
  • TLS 1.2 and above (Requiring OpenSSL >= 1.1.0);
  • TCP/IP server and client connectivity;
  • Unix Socket server and client connectivity;
  • Hot Restarts (using the USR1 signal and without hot deployment);
  • Custom protocol authoring;
  • Sequel and ActiveRecord forking protection;
  • and more!

Since iodine wraps much of the C facil.io framework for Ruby:

  • Iodine can handle thousands of concurrent connections (tested with more then 20K connections on Linux)!

  • Iodine is ideal for Linux/Unix based systems (i.e. macOS, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO completion events, which are very different).

Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.3 and up... it should support the whole Ruby 2.x and 3.x MRI family, but CI tests start at Ruby 2.3.

Note: iodine does not support streaming when using Rack. It's recommended to avoid blocking the server when using body.each since the each loop will block iodine's thread until it's finished and iodine won't send any data before the loop is done.

Iodine - a fast & powerful HTTP + WebSockets server with native Pub/Sub

Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the Rack interface specifications and the Websocket draft extension.

With Iodine.listen service: :http it's possible to run multiple HTTP applications (but please remember not to set more than a single application on a single TCP/IP port).

Iodine also supports native process cluster Pub/Sub and a native RedisEngine to easily scale iodine's Pub/Sub horizontally.

Known Issues and Reporting Issues

See the GitHub Open Issues list for known issues and to report new issues.

Installing and Running Iodine

Install iodine on any Linux / BSD / macOS system using:

gem install iodine

Using the iodine server is easy, simply add iodine as a gem to your Rails / Sinatra / Rack application's Gemfile:

gem 'iodine', '~>0.7'

Then start your application from the command-line / terminal using iodine:

bundler exec iodine

Installing with SSL/TLS

Note: iodine has known issues with the TLS/SSL support. TLS/SSL should NOT be used in production (see issues #95 and #94).

Make sure to update OpenSSL to the latest version before installing Ruby (rbenv should do this automatically).

To avoid name resolution conflicts, iodine will bind to the same OpenSSL version Ruby is bound to. To use SSL/TLS this should be OpenSSL >= 1.1.0 or LibreSSL >= 2.7.4.

Verbose installation should provide a confirmation message, such as:

$ gem install iodine -f -V
...
checking for -lcrypto... yes
checking for -lssl... yes
Detected OpenSSL library, testing for version.
Confirmed OpenSSL to be version 1.1.0 or above (OpenSSL 1.1.0j  20 Nov 2018)...
* Compiling with HAVE_OPENSSL.
...

The installation script tests for OpenSSL 1.1.0 and above. However, this testing approach sometimes provides false positives. If TLS isn't required, install with NO_SSL=1. i.e.:

NO_SSL=1 bundler exec iodine

Running with Rails

On Rails:

  1. Replace the puma gem with the iodine gem.

  2. Remove the config/puma.rb file (or comment out the code).

  3. Optionally, it's possible to add a config/initializers/iodine.rb file. For example:

    # Iodine setup - use conditional setup to allow command-line arguments to override these:
    if(defined?(Iodine))
      Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if Iodine.threads.zero?
      Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if Iodine.workers.zero?
      Iodine::DEFAULT_SETTINGS[:port] ||= ENV.fetch("PORT") if ENV.fetch("PORT")
    end

When using native WebSockets with Rails, middle-ware is probably the best approach. A guide for this approach will, hopefully, get published in the future.

Note: command-line instructions (CLI) should be the preferred way for configuring iodine, allowing for code-less configuration updates.

Optimizing Iodine's Concurrency

To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires.

Iodine will calculate, when possible, a good enough default concurrency model for fast applications. See if this works for your application or customize according to the application's needs.

Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes:

bundler exec iodine -p $PORT -t 16 -w 4

The environment variables THREADS and WORKERS are automatically recognized when iodine is first required, allowing environment specific customization. i.e.:

export THREADS=16
export WORKERS=-1 # negative values are fractions of CPU cores.
bundler exec iodine -p $PORT

Negative values are evaluated as "CPU Cores / abs(Value)". i.e., on an 8 core CPU machine, this will produce 4 worker processes with 2 threads per worker:

bundler exec iodine -p $PORT -t 2 -w -2

Heap Fragmentation Protection

Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC).

This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space.

It is still recommended to consider jemalloc or other allocators that also help mitigate heap fragmentation issues.

Static file serving support

Iodine supports an internal static file service that bypasses the Ruby layer and serves static files directly from "C-land".

This means that iodine won't lock Ruby's GVL when sending static files. The files will be sent directly, allowing for true native concurrency.

Since the Ruby layer is unaware of these requests, logging can be performed by turning iodine's logger on.

To use native static file service, setup the public folder's address before starting the server.

This can be done when starting the server from the command line:

bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder

Or using a simple Ruby script. i.e. (a my_server.rb example):

require 'iodine'
# static file service
Iodine.listen, service: :http, public: '/my/public/folder'
# for static file service, we only need a single thread and a single worker.
Iodine.threads = 1
Iodine.start

To enable logging from the command line, use the -v (verbose) option:

bundler exec iodine -p $PORT -t 16 -w 4 -www /my/public/folder -v

X-Sendfile

When a public folder is assigned (the static file server is active), iodine automatically adds support for the X-Sendfile header in any Ruby application response.

This allows Ruby to send very large files using a very small memory footprint and usually leverages the sendfile system call.

i.e. (example config.ru for iodine):

app = proc do |env|
  request = Rack::Request.new(env)
  if request.path_info == '/source'.freeze
    [200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []]
  elsif request.path_info == '/file'.freeze
    [200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)]
  else
    [200, { 'Content-Type' => 'text/html',
            'Content-Length' => request.path_info.length.to_s },
     [request.path_info]]
 end
end
# # optional:
# use Rack::Sendfile
run app

Benchmark localhost:3000/source to experience the X-Sendfile extension at work.

Pre-Compressed assets / files

Rails does this automatically when compiling assets, which is: gzip your static files.

Iodine will automatically recognize and send the gz version if the client (browser) supports the gzip transfer-encoding.

For example, to offer a compressed version of style.css, run (in the terminal):

$  gzip -k -9 style.css

This results in both files, style.css (the original) and style.css.gz (the compressed).

When a browser that supports compressed encoding (which is most browsers) requests the file, iodine will recognize that a pre-compressed option exists and will prefer the gzip compressed version.

It's as easy as that. No extra code required.

Special HTTP Upgrade and SSE support

Iodine's HTTP server implements the WebSocket/SSE Rack Specification Draft, supporting native WebSocket/SSE connections using Rack's env Hash.

This promotes separation of concerns, where iodine handles all the Network related logic and the application can focus on the API and data it provides.

Upgrading an HTTP connection can be performed either using iodine's native WebSocket / EventSource (SSE) support with env['rack.upgrade?'] or by implementing your own protocol directly over the TCP/IP layer - be it a WebSocket flavor or something completely different - using env['upgrade.tcp'].

EventSource / SSE

Iodine treats EventSource / SSE connections as if they were a half-duplex WebSocket connection, using the exact same API and callbacks as WebSockets.

When an EventSource / SSE request is received, iodine will set the Rack Hash's upgrade property to :sse, so that: env['rack.upgrade?'] == :sse.

The rest is detailed in the WebSocket support section.

WebSockets

When a WebSocket connection request is received, iodine will set the Rack Hash's upgrade property to :websocket, so that: env['rack.upgrade?'] == :websocket

To "upgrade" the HTTP request to the WebSockets protocol (or SSE), simply provide iodine with a WebSocket Callback Object instance or class: env['rack.upgrade'] = MyWebsocketClass or env['rack.upgrade'] = MyWebsocketClass.new(args)

Iodine will adopt the object, providing it with network functionality (methods such as write, defer and close will become available) and invoke it's callbacks on network events.

Here is a simple chat-room example we can run in the terminal (irb) or easily paste into a config.ru file:

require 'iodine'
module WebsocketChat
  def on_open client
    # Pub/Sub directly to the client (or use a block to process the messages)
    client.subscribe :chat
    # Writing directly to the socket
    client.write "You're now in the chatroom."
  end
  def on_message client, data
    # Strings and symbol channel names are equivalent.
    client.publish "chat", data
  end
  extend self
end
APP = Proc.new do |env|
  if env['rack.upgrade?'.freeze] == :websocket 
    env['rack.upgrade'.freeze] = WebsocketChat 
    [0,{}, []] # It's possible to set cookies for the response.
  elsif env['rack.upgrade?'.freeze] == :sse
    puts "SSE connections can only receive data from the server, the can't write." 
    env['rack.upgrade'.freeze] = WebsocketChat
    [0,{}, []] # It's possible to set cookies for the response.
  else
    [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Welcome Home"] ]
  end
end
# Pus/Sub can be server oriented as well as connection bound
Iodine.subscribe(:chat) {|ch, msg| puts msg if Iodine.master? }
# By default, Pub/Sub performs in process cluster mode.
Iodine.workers = 4
# # in irb:
Iodine.listen service: :http, public: "www/public", handler: APP
Iodine.start
# # or in config.ru
run APP

Native Pub/Sub with optional Redis scaling

Iodine's core, facil.io offers a native Pub/Sub implementation that can be scaled across machine boundaries using Redis.

The default implementation covers the whole process cluster, so a single cluster doesn't need Redis

Once a single iodine process cluster isn't enough, horizontal scaling for the Pub/Sub layer is as simple as connecting iodine to Redis using the -r <url> from the command line. i.e.:

$ iodine -w -1 -t 8 -r redis://localhost

It's also possible to initialize the iodine<=>Redis link using Ruby, directly from the application's code:

# initialize the Redis engine for each iodine process.
if ENV["REDIS_URL"]
  Iodine::PubSub.default = Iodine::PubSub::Redis.new(ENV["REDIS_URL"])
else
  puts "* No Redis, it's okay, pub/sub will still run on the whole process cluster."
end
# ... the rest of the application remains unchanged.

Iodine's Redis client can also be used for asynchronous Redis command execution. i.e.:

if(Iodine::PubSub.default.is_a? Iodine::PubSub::Redis)
  # Ask Redis about all it's client connections and print out the reply.
  Iodine::PubSub.default.cmd("CLIENT LIST") { |reply| puts reply }
end

Pub/Sub Details and Limitations:

  • Iodine's Redis client does not support multiple databases. This is both because database scoping is ignored by Redis during pub/sub and because Redis Cluster doesn't support multiple databases. This indicated that multiple database support just isn't worth the extra effort and performance hit.

  • The iodine Redis client will use two Redis connections for the whole process cluster (a single publishing connection and a single subscription connection), minimizing the Redis load and network bandwidth.

  • Connections will be automatically re-established if timeouts or errors occur.

Hot Restart

Iodine will "hot-restart" the application by shutting down and re-spawning the worker processes.

This will clear away any memory fragmentation concerns and other issues that might plague a long running worker process or ruby application.

To hot-restart iodine, send the SIGUSR1 signal to the root process.

The following code will hot-restart iodine every 4 hours when iodine is running in cluster mode:

Iodine.run_every(4 * 60 * 60 * 1000) do
  Process.kill("SIGUSR1", Process.pid) unless Iodine.worker?
end

Since the master / root process doesn't handle any requests (it only handles pub/sub and house-keeping), it's memory map and process data shouldn't be as affected and the new worker processes should be healthier and more performant.

Note: This will not re-load the application (any changes to the Ruby code require an actual restart).

Optimized HTTP logging

By default, iodine is pretty quiet. Some messages are logged to stderr, but not many.

However, HTTP requests can be logged using iodine's optimized logger to stderr. Iodine will optimize the log output by caching the output time string which updates every second rather than every request.

This can be performed by setting the -v flag during startup, i.e.:

bundler exec iodine -p $PORT -t 16 -w 4 -v -www /my/public/folder

The log output can be redirected to a file:

bundler exec iodine -p $PORT -v  2>my_log.log

The log output can also be redirected to a stdout:

bundler exec iodine -p $PORT -v  2>&1

Built-in support for Sequel and ActiveRecord

It's a well known fact that Database connections require special attention when using fork-ing servers (multi-process servers) such as Puma, Passenger (Pro) and iodine.

However, it's also true that these issues go unnoticed by many developers, since application developers are (rightfully) focused on the application rather than the infrastructure.

With iodine, there's no need to worry.

Iodine provides built-in fork handling for both ActiveRecord and Sequel, in order to protect against these possible errors.

Client Support

Iodine supports raw (TCP/IP and Unix Sockets) client connections as well as WebSocket connections.

This can be utilized for communicating across micro services or taking advantage of persistent connection APIs such as ActionCable APIs, socket.io APIs etc'.

Here is an example WebSocket client that will connect to the WebSocket.org echo test service and send a number of pre-programmed messages.

require 'iodine'

# The client class
class EchoClient

  def on_open(connection)
    @messages = [ "Hello World!",
      "I'm alive and sending messages",
      "I also receive messages",
      "now that we all know this...",
      "I can stop.",
      "Goodbye." ]
    send_one_message(connection)
  end

  def on_message(connection, message)
    puts "Received: #{message}"
    send_one_message(connection)
  end

  def on_close(connection)
    # in this example, we stop iodine once the client is closed
    puts "* Client closed."
    Iodine.stop
  end

  # We use this method to pop messages from the queue and send them
  #
  # When the queue is empty, we disconnect the client.
  def send_one_message(connection)
    msg = @messages.shift
    if(msg)
      connection.write msg
    else
      connection.close
    end
  end
end

Iodine.threads = 1
Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40
Iodine.start

TLS >= 1.2 support

Requires OpenSSL >= 1.1.0. On Heroku, requires heroku-18.

Iodine supports secure connections fore TLS version 1.2 and up (depending on the OpenSSL version).

A self signed certificate is available using the -tls flag from the command-line.

PEM encoded certificates (which is probably the most common format) can be loaded from the command-line (-tls-cert and -tls-key) or dynamically (using Iodine::TLS).

The TLS API is simplified but powerful, supporting the ALPN extension and peer verification (which client connections really should leverage).

When enabling peer verification for server connections (using Iodine::TLS#trust), clients will be required to submit a trusted certificate in order to connect to the server.

TCP/IP (raw) sockets

Upgrading to a custom protocol (i.e., in order to implement your own WebSocket protocol with special extensions) is available when neither WebSockets nor SSE connection upgrades were requested. In the following (terminal) example, we'll use an echo server without direct socket echo:

require 'iodine'
class MyProtocol
  def on_message client, data
    # regular socket echo - NOT websockets
    client.write data
  end
end
APP = Proc.new do |env|
  if env["HTTP_UPGRADE".freeze] =~ /echo/i.freeze
    env['upgrade.tcp'.freeze] = MyProtocol.new
    # an HTTP response will be sent before changing protocols.
    [101, { "Upgrade" => "echo" }, []]
  else
    [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Welcome Home"] ]
  end
end
# # in irb:
Iodine.listen service: :http, public: "www/public", handler: APP
Iodine.threads = 1
Iodine.start
# # or in config.ru
run APP

How does it compare to other servers?

In my tests, pitching Iodine against Puma, Iodine was anywhere between x1.5 and more than x10 faster than Puma (depending on use-case and settings).

Such a big difference is suspect and I recommend that you test it yourself - even better if you test performance using your own application and a number of possible different settings (how many threads per CPU core? how many worker processes? middleware vs. server request logging, etc').

I recommend benchmarking the performance for yourself using wrk or ab:

$ wrk -c200 -d4 -t2 http://localhost:3000/
# or
$ ab -n 100000 -c 200 -k http://127.0.0.1:3000/

The best application to use for benchmarking is your actual application. Or, you could create a simple config.ru file with a hello world app:

App = Proc.new do |env|
   [200,
     {   "Content-Type" => "text/html".freeze,
         "Content-Length" => "16".freeze },
     ['Hello from Rack!'.freeze]  ]
end

run App

Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes, 4 threads):

$ RACK_ENV=production iodine -p 3000 -t 4 -w 4
# vs.
$ RACK_ENV=production puma -p 3000 -t 4 -w 4
# Review the `iodine -?` help for more command line options.

It's recommended that the servers (Iodine/Puma) and the client (wrk/ab) run on separate machines.

It is worth noting that iodine can also speed up logging by replacing the logging middleware with iodine -v. This approach uses less memory and improves performance at the expense of fuzzy timing and some string caching.

On my machine, testing with the logging functionality enabled, iodine was more then 10 times faster than puma (60.9K req/sec vs. 5.3K req/sec)

A few notes

Iodine's upgrade / callback design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems), etc. This also allows us to use middleware without interfering with connection upgrades and provides backwards compatibility.

Iodine's HTTP server imposes a few restrictions for performance and security reasons, such as limiting each header line to 8Kb. These restrictions shouldn't be an issue and are similar to limitations imposed by Apache or Nginx.

If you still want to use Rack's hijack API, iodine will support you - but be aware that you will need to implement your own reactor and thread pool for any sockets you hijack, as well as a socket buffer for non-blocking write operations (why do that when you can write a protocol object and have the main reactor manage the socket?).

Installation

To install iodine, simply install the the iodine gem:

$ gem install iodine

Iodine is written in C and allows some compile-time customizations, such as:

  • FIO_FORCE_MALLOC - avoids iodine's custom memory allocator and use malloc instead (mostly used when debugging iodine or when using a different memory allocator).

  • FIO_MAX_SOCK_CAPACITY - limits iodine's maximum client capacity. Defaults to 131,072 clients.

  • FIO_USE_RISKY_HASH - replaces SipHash with RiskyHash for iodine's internal hash maps.

    Since iodine hash maps have internal protection against collisions and hash flooding attacks, it's possible for iodine to leverage RiskyHash, which is faster than SipHash.

    By default, SipHash will be used. This is a community related choice, since the community seems to believe a hash function should protect the hash map rather than it being enough for a hash map implementation to be attack resistance.

  • HTTP_MAX_HEADER_COUNT - limits the number of headers the HTTP server will accept before disconnecting a client (security). Defaults to 128 headers (permissive).

  • HTTP_MAX_HEADER_LENGTH - limits the number of bytes allowed for a single header (pre-allocated memory per connection + security). Defaults to 8Kb per header line (normal).

  • HTTP_BUSY_UNLESS_HAS_FDS - requires at least X number of free file descriptors (for new database connections, etc') before accepting a new HTTP client.

  • FIO_ENGINE_POLL - prefer the poll system call over epoll or kqueue (not recommended).

  • FIO_LOG_LENGTH_LIMIT - sets the limit on iodine's logging messages (uses stack memory, so limits must be reasonable. Defaults to 2048.

  • FIO_TLS_PRINT_SECRET - if true, the OpenSSL master key will be printed as debug message level log. Use only for testing (with WireShark etc'), never in production! Default: false.

These options can be used, for example, like so:

  gem install iodine -- \
      --with-cflags=\"-DHTTP_MAX_HEADER_LENGTH=48000 -DFIO_FORCE_MALLOC=1 -DHTTP_MAX_HEADER_COUNT=64\"

More possible compile time options can be found in the facil.io documentation.

Evented oriented design with extra safety

Iodine is an evented server, similar in its architecture to nginx and puma. It's different than the simple "thread-per-client" design that is often taught when we begin to learn about network programming.

By leveraging epoll (on Linux) and kqueue (on BSD), iodine can listen to multiple network events on multiple sockets using a single thread.

All these events go into a task queue, together with the application events and any user generated tasks, such as ones scheduled by Iodine.run.

In pseudo-code, this might look like this

QUEUE = Queue.new

def server_cycle
    if(QUEUE.empty?)
      QUEUE << get_next_32_socket_events # these events schedule the proper user code to run
    end
    QUEUE << server_cycle
end

def run_server
      while ((event = QUEUE.pop))
            event.shift.call(*event)
      end
end

In pure Ruby (without using C extensions or Java), it's possible to do the same by using select... and although select has some issues, it could work well for lighter loads.

The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything...

...but single threaded mode should probably be avoided.

It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's short tasks on the same thread.

The thread pool is there to help slow user code.

The slower your application code, the more threads you will need to keep the server running in a responsive manner (note that responsiveness and speed aren't always the same).

To make a thread pool easier and safer to use, iodine makes sure that no connection task / callback is called concurrently for the same connection.

For example, a is a WebSocket connection is already busy in it's on_message callback, no other messages will be forwarded to the callback until the current callback returns.

Free, as in freedom (BYO beer)

Iodine is free and open source, so why not take it out for a spin?

It's installable just like any other gem on Ruby MRI, run:

$ gem install iodine

If building the native C extension fails, please note that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (ruby.h and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get them.

If you have the development headers but still can't compile the iodine extension, open an issue with any messages you're getting and I'll be happy to look into it.

Mr. Sandman, write me a server

Iodine allows custom TCP/IP server authoring, for those cases where we need raw TCP/IP (UDP isn't supported just yet).

Here's a short and sweet echo server - No HTTP, just use telnet:

USE_TLS = false

require 'iodine'

# an echo protocol with asynchronous notifications.
class EchoProtocol
  # `on_message` is called when data is available.
  def on_message client, buffer
    # writing will never block and will use a buffer written in C when needed.
    client.write buffer
    # close will be performed only once all the data in the write buffer
    # was sent. use `force_close` to close early.
    client.close if buffer =~ /^bye[\r\n]/i
    # run asynchronous tasks... after a set number of milliseconds
    Iodine.run_after(1000) do
      # or schedule the task immediately
      Iodine.run do
        puts "Echoed data: #{buffer}"
      end
    end
  end
end

tls = USE_TLS ? Iodine::TLS.new("localhost") : nil

# listen on port 3000 for the echo protocol.
Iodine.listen(port: "3000", tls: tls) { EchoProtocol.new }
Iodine.threads = 1
Iodine.workers = 1
Iodine.start

Or a nice plain text chat room (connect using telnet or nc ):

require 'iodine'

# a chat protocol with asynchronous notifications.
class ChatProtocol
  def initialize nickname = "guest"
    @nickname = nickname
  end
  def on_open client
    client.subscribe :chat
    client.publish :chat, "#{@nickname} joined chat.\n"
    client.timeout = 40
  end
  def on_close client
    client.publish :chat, "#{@nickname} left chat.\n"
  end
  def on_shutdown client
    client.write "Server is shutting down... try reconnecting later.\n"
  end
  def on_message client, buffer
    if(buffer[-1] == "\n")
      client.publish :chat, "#{@nickname}: #{buffer}"
    else
      client.publish :chat, "#{@nickname}: #{buffer}\n"
    end
    # close will be performed only once all the data in the outgoing buffer
    client.close if buffer =~ /^bye[\r\n]/i
  end
  def ping client
    client.write "(ping) Are you there, #{@nickname}...?\n"
  end
end

# an initial login protocol
class LoginProtocol
  def on_open client
    client.write "Enter nickname to log in to chat room:\n"
    client.timeout = 10
  end
  def ping client
    client.write "Time's up... goodbye.\n"
    client.close
  end
  def on_message client, buffer
    # validate nickname and switch connection callback to ChatProtocol
    nickname = buffer.split("\n")[0]
    while (nickname && nickname.length() > 0 && (nickname[-1] == '\n' || nickname[-1] == '\r'))
      nickname = nickname.slice(0, nickname.length() -1)
    end
    if(nickname && nickname.length() > 0 && buffer.split("\n").length() == 1)
      chat = ChatProtocol.new(nickname)
      client.handler = chat
    else
      client.write "Nickname error, try again.\n"
      on_open client
    end
  end
end

# listen on port 3000
Iodine.listen(port: 3000) { LoginProtocol.new }
Iodine.threads = 1
Iodine.workers = 1
Iodine.start

Why not EventMachine?

EventMachine attempts to give the developer access to the network layer while Iodine attempts to abstract the network layer away and offer the developer a distraction free platform.

You can go ahead and use EventMachine if you like. They're doing amazing work on that one and it's been used a lot in Ruby-land... really, tons of good developers and people on that project.

But why not take iodine out for a spin and see for yourself?

Can I contribute?

Yes, please, here are some thoughts:

  • I'm really not good at writing automated tests and benchmarks, any help would be appreciated. I keep testing manually and that's less then ideal (and it's mistake prone).

  • PRs or issues related to the facil.io C framework should be placed in the facil.io repository.

  • Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/iodine.

  • If we can write a Java wrapper for the facil.io C framework, it would be nice... but it could be as big a project as the whole gem, as a lot of minor details are implemented within the bridge between these two languages.

  • If you love the project or thought the code was nice, maybe helped you in your own project, drop me a line. I'd love to know.

Running the Tests

Running this task will compile the C extensions then run RSpec tests:

bundle exec rake spec

License

The gem is available as open source under the terms of the MIT License.

iodine's People

Contributors

adam12 avatar akostadinov avatar arkan avatar bjeanes avatar boazsegev avatar brandondrew avatar e-pavlica avatar elia avatar elskwid avatar epigene avatar fgoepel avatar fonsan avatar frozenfoxx avatar giovannibonetti avatar ianks avatar janbiedermann avatar janko avatar jmoriau avatar jomei avatar lucaskuan avatar ojab avatar pgeraghty avatar raxoft avatar wa9ace avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iodine's Issues

Iodine doesn't pass external SCRIPT_NAME env variable

We want to switch to Iodine for a rails app that is behind a reverse proxy. The app is mounted on a subfolder of the domain using nginx so we use the SCRIPT_NAME environment variable to make the url helpers return the correct urls.

When using Iodine to serve the app it seems that the SCRIPT_NAME variable is set to "" instead of using the value supplied from the system. From what I could find it seems to happen here:

add_str_to_env(env_template_no_upgrade, "SCRIPT_NAME", "");

This causes the url helpers to create incorrect urls.

Is there a way to configure iodine to use a different SCRIPT_NAME or make it use the system supplied value?

Iodine refusing to log anything because it's too long

I'm seeing messages like the following:
ERROR: log line output too long (can't write).ERROR:

I believe this is because of this issue:

#ifndef FIO_LOG_LENGTH_LIMIT

Any plans to make this allocate on the heap instead? Logging is broken for me in this case (with the hard limit).

If it's too tricky to make it allocate space on the heap when the log entry is too long, then maybe a separate macro for "verbose logging" or something would work?

Callback handler class is used directly without creating a new instance

Websocket spec says that If the callback handler is a Class object, the server will create a new instance of that class., but according to:

require 'iodine'
require 'websocket-client-simple'

class ClientSocket # :nodoc:
  def self.on_open(client)
    p client
  end
end

class App # :nodoc:
  def call(env)
    unless env['rack.upgrade?'] == :websocket
      return [404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
    end

    env['rack.upgrade'] = ClientSocket

    [0, {}, []]
  end
end

Thread.new do
  Iodine::Rack.run(
    App.new,
    Port: '3099', Address: '127.0.0.1'
  )
end

WebSocket::Client::Simple.connect('ws://127.0.0.1:3099')

new instance is not created and ClientSocket.on_open is called (iodine-0.7.7).

Random segfault after fio_flush_all

Hey @boazsegev, hope all is well ๐Ÿ˜„

We are getting a random segfault after an acceptance tests finish in capybara. I believe capybara may be killing the Iodine thread (which is should do automatically), at which point Iodine sometimes segfaults in fio_flush_all

Here is the backtrace info:

ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-linux]

-- Machine register context ------------------------------------------------
 RIP: 0x00007f98fb43063e RBP: 0x00007f98eb9fd6d0 RSP: 0x00007f98eb9fd6b0
 RAX: 0x0000000000000000 RBX: 0x0000000000000000 RCX: 0x00007f993bc10010
 RDX: 0xf2f0ab6d387d4400 RDI: 0x0000000000000000 RSI: 0xf2f0ab6d387d4400
  R8: 0x00007f98eb9ff700  R9: 0x00007f98eb9ff700 R10: 0xfffffffffffff942
 R11: 0x0000000000000000 R12: 0x00007f98ec5f9ace R13: 0x00007f98ec5f9acf
 R14: 0x00007f98eb9ff700 R15: 0x0000000000000000 EFL: 0x0000000000010202

-- C level backtrace information -------------------------------------------

bin/paraspec [worker-6](0x262d84) [0x55b29fc4bd84]
bin/paraspec [worker-6](0x259090) [0x55b29fc42090]
bin/paraspec [worker-6](0x138612) [0x55b29fb21612]
/usr/lib/libpthread.so.0(__restore_rt+0x0) [0x7f99036f33c0]
/home/ianks/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/iodine-0.7.27/lib/iodine/iodine.so(fio_flush_all+0x3e) [0x7f98fb43063e]
/home/ianks/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/iodine-0.7.27/lib/iodine/iodine.so(0xa2bef) [0x7f98fb4b9bef]
/usr/lib/libpthread.so.0(0x7a9d) [0x7f99036e8a9d]
/usr/lib/libc.so.6(clone+0x43) [0x7f9902f21b23]

I'm more than happy to provide other infomation that might be helpful as well, but my C is a little rusty ๐Ÿ˜„

Would it be feasible to implement request coalescing?

I'm having a great experience with iodine in production in my (small) company and I'm wondering how to extend Iodine to solve an issue we are having. We have some misbehaving (web) clients that ocasionally send a burst of duplicated requests to an expensive endpoint - a server even crashed because of that - and we're looking into a solution to avoid wasting resources with them.

The easier solution is to use something like rack-attack and just drop these requests from misbehaving clients with a 429 error. However, I was wondering if there is a way to implement some request coalescing logic like this to avoid showing an error page when this kind of thing happens. It would have to be implemented either in the load balancer (which is not managed by us) or in the application server. Another way of solving the problem would be inserting another reverse proxy in front of the application like Nginx or Caddy, but I wonder if that would be overkill.

Is there a way of doing some kind of request coalescing / collapsing with Iodine? ๐Ÿค”

Fails to build on FreeBSD 11.1

First off - thanks for this great app server, and your work to push the Rack Upgrade standard forward!

I'm having issues building this gem on FreeBSD. I've seen reports that we need to use a #undef _POSIX_C_SOURCE which I tried to no-avail.

Just out of curiosity, I went back through the released versions and the latest one that I could build was 0.2.17. Some of them failed with the following message, and some of them complained about error: incomplete definition of type 'struct sockaddr_in'.

current directory: /usr/local/lib/ruby/gems/2.3.0/gems/iodine-0.5.1/ext/iodine
/usr/local/bin/ruby -r ./siteconf20180511-34785-1kh3kyg.rb extconf.rb
checking for kevent()... yes
checking for clang... yes
testing clang for stdatomic support...
using clang compiler v. 4.2.1
.
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/lib/ruby/gems/2.3.0/extensions/x86_64-freebsd-11/2.3.0-static/iodine-0.5.1/mkmf.log

current directory: /usr/local/lib/ruby/gems/2.3.0/gems/iodine-0.5.1/ext/iodine
make "DESTDIR=" clean

current directory: /usr/local/lib/ruby/gems/2.3.0/gems/iodine-0.5.1/ext/iodine
make "DESTDIR="
compiling defer.c
compiling evio.c
compiling evio_callbacks.c
compiling evio_epoll.c
compiling evio_kqueue.c
In file included from evio_kqueue.c:23:
/usr/include/sys/event.h:61:2: error: unknown type name 'u_short'; did you mean 'short'?
        u_short         flags;
        ^
/usr/include/sys/event.h:62:2: error: unknown type name 'u_int'
        u_int           fflags;
        ^
2 errors generated.
*** Error code 1

Stop.
make: stopped in /usr/local/lib/ruby/gems/2.3.0/gems/iodine-0.5.1/ext/iodine

make failed, exit code 1
# uname -a
FreeBSD  11.1-RELEASE FreeBSD 11.1-RELEASE #0 r321309: Fri Jul 21 02:08:28 UTC 2017     [email protected]:/usr/obj/usr/src/sys/GENERIC  amd64

Let me know if you need any more information or I can help debug in any way. Cheers.

Very intermittent core dump on FreeBSD x64

Not sure if you can debug from just the backtrace, but here it is. I normally only see it randomly after sending SIGINT, where it feels the process is completely not responding.

#0  0x0000000801e3584a in thr_kill () from /lib/libc.so.7
#1  0x0000000801e35814 in raise () from /lib/libc.so.7
#2  0x0000000801e35789 in abort () from /lib/libc.so.7
#3  0x0000000800a9005e in rb_bug () from /usr/local/lib/libruby24.so.24
#4  0x0000000800b5f1eb in ruby_sig_finalize () from /usr/local/lib/libruby24.so.24
#5  0x0000000801708934 in pthread_sigmask () from /lib/libthr.so.3
#6  0x0000000801707ecf in pthread_getspecific () from /lib/libthr.so.3
#7  <signal handler called>
#8  0x00000008056ab88b in sig_int_handler () from /usr/local/lib/ruby/gems/2.4/gems/iodine-0.6.5/lib/iodine/iodine.so
#9  0x0000000801708926 in pthread_sigmask () from /lib/libthr.so.3
#10 0x0000000801707ecf in pthread_getspecific () from /lib/libthr.so.3
#11 <signal handler called>
#12 0x0000000801eb6a8a in munmap () from /lib/libc.so.7
#13 0x00000008056a760a in facil_libcleanup () from /usr/local/lib/ruby/gems/2.4/gems/iodine-0.6.5/lib/iodine/iodine.so
#14 0x0000000801ea44d5 in __cxa_finalize () from /lib/libc.so.7
#15 0x0000000801e353d1 in exit () from /lib/libc.so.7
#16 0x00000008056ab995 in facil_sentinel_worker_thread () from /usr/local/lib/ruby/gems/2.4/gems/iodine-0.6.5/lib/iodine/iodine.so
#17 0x00000008056eb1ab in defer_thread_start () from /usr/local/lib/ruby/gems/2.4/gems/iodine-0.6.5/lib/iodine/iodine.so
#18 0x0000000800b9403e in rb_thread_call_without_gvl () from /usr/local/lib/libruby24.so.24
#19 0x00000008056eb17a in defer_thread_inGVL () from /usr/local/lib/ruby/gems/2.4/gems/iodine-0.6.5/lib/iodine/iodine.so
#20 0x0000000800b9a0ef in rb_reset_coverages () from /usr/local/lib/libruby24.so.24
#21 0x0000000800b99da5 in rb_reset_coverages () from /usr/local/lib/libruby24.so.24
#22 0x0000000801702bc5 in pthread_create () from /lib/libthr.so.3
#23 0x0000000000000000 in ?? ()

FreeBSD 11.2-RELEASE FreeBSD 11.2-RELEASE #0 r335510: Fri Jun 22 04:32:14 UTC 2018 [email protected]:/usr/obj/usr/src/sys/GENERIC amd64

ruby 2.4.4p296 (2018-03-28 revision 63013) [amd64-freebsd11]

Let me know if there's something better to debug against (core perhaps?) or if I can do anything to help debug this.

Much thanks!

YARD documentation is lacking tags

The YARD documentation in the C files is missing some YARD documentation tags, such as @param, @return...

...I would love help with this.

It's easy to take a source file at a time or maybe even fix the documentation for a single function or two at a time.

Great start for newcomers who want to help in on an open source project.

sending a message right after handshake

Hi again!

We have been struggling with a very weird issue recently (that's also something that triggered us to update iodine).

In our websocket use case, we send an initial message to the server right after the handshake is finished. The message is relatively small, something like {"init": true} (we ask server for some initial data).

Every now and then we don't get anything back as if the message didn't make it at all. It happens more often if we are in the same location as the server (i.e. it's a local machine connection, or local network connection).

In our logs we also don't see anything happening with the on_message callback. If we repeat the message second later we get the reply back normally.

Our educated guess (I don't know C well enough) was that the callbacks for messages are set after the websocket handshake is finished - and hence if we are very quick we can squeeze the initial message on the socket before there is anything listening for it.
Could you please take a look if that's the case? Or maybe there is some other race condition in there?

Dropped connections?

Hi, I trying your server and have one issue. My web application needs to load a bunch of data about issues so I split to ten or so chunks and made parallel requests. Browser process them in batches of two (send 2 request, wait for response, next two, etc.).

When I run puma with -t 1 -w 4, everything is ok. But if I run your server with same settings -t 1 -w 4, some connections are properly handled and some are failed with empty response. With -t 2 -w 4 everything goes well. I'm really not sure what can do it, but it's definitely not a problem with my application because on every other tested server everything is OK (tested with webrick, unicorn, puma, passenger and thin).

Also, have you done some benchmarks? In my scenario with these issues, I have pretty slow requests in general as you can see, it's because this is a pretty complex application, entities are big and with many nested entities. I experienced that your server has significantly slower responses then e.q. puma (about 30 - 50% slower).

screenshot 2017-05-16 13 40 58

Thread safety issue?

I'm not quite sure that it's iodine issue, but I suspect so:

require 'iodine'
require 'net/http'
require 'concurrent'

class ServerTest # :nodoc:
  def self.logs
    @logs ||= Concurrent::Array.new
  end
end

class App # :nodoc:
  def call(_env)
    ServerTest.logs << 'log'
  end
end

Thread.new do
  Iodine::Rack.run(
    App.new,
    Port: '3099', Address: '127.0.0.1'
  )
end

Net::HTTP.get(URI('http://127.0.0.1:3099'))

p ServerTest.logs
# => []

the same test with puma shows expected ["log"]

`each` is broken on Linux

For some reason, each is broken on Linux, while working flawlessly on macOS.

Tests have yielded no apparent reason so far, though it might have something to do with closing connections (and might not).

The error received is "segfault" for address 0x00000....04

Attempting to reproduce with C code (no Ruby) did not yield any errors nor segmentation faults.

The issue is being researched, but is (of yet) unresolved.

Segfault in listen gem

I'm not sure what changed to cause this to start happening, but I'm seeing it all the time now in both iodine 0.7.25 and 0.7.31. The seg fault occurs immediately after starting rails.

=> Booting Rack
=> Rails 5.2.2.1 application starting in development on http://0.0.0.0:1338
=> Run `rails server -h` for more startup options
Plezi 0.16.4 as Middleware
Running Plezi version: 0.16.4
INFO: Listening on port 1338
INFO: Starting up Iodine:
 * Iodine 0.7.31
 * Ruby 2.6.2
 * facil.io 0.7.0.beta9 (kqueue)
 * 7 Workers X 8 Threads per worker.
 * Maximum 24560 open files / sockets per worker.
 * Master (root) process: 13517.

INFO: Server is running 7 workers X 8 threads with facil.io 0.7.0.beta9 (kqueue)
* Detected capacity: 24560 open file limit
* Root pid: 13517
* Press ^C to stop

INFO: 13534 is running.
INFO: 13536 is running.
INFO: 13538 is running.
INFO: 13540 is running.
INFO: 13542 is running.
INFO: 13544 is running.
INFO: 13546 is running.
I, [2019-05-20T14:27:38.668648 #13544]  INFO -- : Started GET "/endpoint" for 10.17.31.207 at 2019-05-20 14:27:38 -0600
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:53: [BUG] Segmentation fault at 0x000000010a3f5a3a
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin17]

-- Crash Report log information --------------------------------------------
   See Crash Report log file under the one of following:
     * ~/Library/Logs/DiagnosticReports
     * /Library/Logs/DiagnosticReports
   for more details.
Don't forget to include the above Crash Report log file in bug reports.

-- Control frame information -----------------------------------------------
c:0013 p:---- s:0062 e:000061 CFUNC  :open
c:0012 p:---- s:0059 e:000058 CFUNC  :entries
c:0011 p:0026 s:0051 e:000053 METHOD /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:53
c:0010 p:0005 s:0048 e:000047 METHOD /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/symlink_detector.rb:27
c:0009 p:0021 s:0042 e:000041 METHOD /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record.rb:103
c:0008 p:0070 s:0034 e:000033 METHOD /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record.rb:68
c:0007 p:0009 s:0028 e:000027 BLOCK  /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:76
c:0006 p:0017 s:0025 e:000024 METHOD /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:103
c:0005 p:0008 s:0018 e:000017 BLOCK  /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:76 [FINISH]
c:0004 p:---- s:0014 e:000013 CFUNC  :each
c:0003 p:0011 s:0010 e:000009 BLOCK  /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:75
c:0002 p:0007 s:0006 e:000005 BLOCK  /Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/internals/thread_pool.rb:6 [FINISH]
c:0001 p:---- s:0003 e:000002 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/internals/thread_pool.rb:6:in `block in add'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:75:in `block in start'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:75:in `each'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:76:in `block (2 levels) in start'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:103:in `_timed'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/adapter/base.rb:76:in `block (3 levels) in start'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record.rb:68:in `build'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record.rb:102:in `_fast_build_dir'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:18:in `children'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:53:in `_entries'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:53:in `entries'
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/listen-3.1.5/lib/listen/record/entry.rb:53:in `open'

-- Machine register context ------------------------------------------------
 rax: 0x000000010a3f5a38 rbx: 0x00007fdff35efac0 rcx: 0x0000700003cf00ac
 rdx: 0x0000000000000000 rdi: 0x00007fdff35efac0 rsi: 0x0000000000000001
 rbp: 0x0000700003cee250 rsp: 0x0000700003cede10  r8: 0x0000000000000001
  r9: 0x0000000000000006 r10: 0x0000000000000600 r11: 0x20f5dd11954000fa
 r12: 0x00007fdff35efac0 r13: 0x0000000000000000 r14: 0x0000000000000002
 r15: 0x000000010a3f5a3c rip: 0x00007fff63bd990a rfl: 0x0000000000010202

-- C level backtrace information -------------------------------------------
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_vm_bugreport+0x82) [0x107596ab2]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_bug_context+0x1d8) [0x1073f0e28]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(sigsegv+0x51) [0x1074ffbc1]
/usr/lib/system/libsystem_platform.dylib(_sigtramp+0x1d) [0x7fff63bb6b5d]
/usr/lib/system/libsystem_trace.dylib(_os_log_preferences_refresh+0x4c) [0x7fff63bd990a]
/usr/lib/system/libsystem_trace.dylib(0x7fff63bda13d) [0x7fff63bda13d]
/usr/lib/system/libsystem_info.dylib(_gai_nat64_v4_address_requires_synthesis+0x62) [0x7fff63ae7692]
/usr/lib/system/libsystem_info.dylib(0x7fff63ae6aa0) [0x7fff63ae6aa0]
/usr/lib/system/libsystem_info.dylib(0x7fff63ac5847) [0x7fff63ac5847]
/usr/lib/system/libsystem_info.dylib(0x7fff63ac4f77) [0x7fff63ac4f77]
/usr/lib/system/libsystem_info.dylib(0x7fff63ac4e7d) [0x7fff63ac4e7d]
/usr/local/opt/postgresql/lib/libpq.5.dylib(connectDBStart+0x145) [0x10aada955]
/usr/local/opt/postgresql/lib/libpq.5.dylib(0x10aad9b5b) [0x10aad9b5b]
/usr/local/opt/postgresql/lib/libpq.5.dylib(0x10aad9afe) [0x10aad9afe]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/pg-1.1.4/lib/pg_ext.bundle(gvl_PQconnectdb_skeleton+0x11) [0x10aa99d11]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(call_without_gvl+0xb0) [0x10753c420]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/pg-1.1.4/lib/pg_ext.bundle(gvl_PQconnectdb+0x2d) [0x10aa99ced]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/pg-1.1.4/lib/pg_ext.bundle(pgconn_init+0x79) [0x10aa9ee99]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(vm_call0_body+0x256) [0x10757bae6]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_call0+0xf5) [0x107591ae5]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_funcallv+0x31) [0x10757a021]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_class_s_new+0x29) [0x10747b629]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(vm_call_cfunc+0x15b) [0x107589d7b]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(vm_exec_core+0x33df) [0x107572a5f]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_vm_exec+0xaa2) [0x107584932]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_call0+0xf5) [0x107591ae5]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_funcallv+0x31) [0x10757a021]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_class_s_new+0x29) [0x10747b629]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(vm_call_cfunc+0x15b) [0x107589d7b]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(vm_exec_core+0x33df) [0x107572a5f]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_vm_exec+0xaa2) [0x107584932]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_call0+0xf5) [0x107591ae5]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_funcallv+0x31) [0x10757a021]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_protect+0x154) [0x1073fc944]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(iodine_call2+0x65) [0x10aa2fe05]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(iodine_handle_request_in_GVL+0xf40) [0x10aa36f10]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(rb_thread_call_with_gvl+0x57) [0x10753cba7]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(iodine_enterGVL+0x39) [0x10aa2fc89]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(on_rack_upgrade+0x5a) [0x10aa3485a]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(http_on_request_handler______internal+0x368) [0x10aa1bdc8]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(http1_on_request+0x1a) [0x10aa1a5ca]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(http1_fio_parser_fn+0xabb) [0x10aa1b9bb]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(http1_consume_data+0x17a) [0x10aa1a4fa]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(deferred_on_data+0x1a3) [0x10a9cd393]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(fio_defer_perform_single_task_for_queue+0x19c) [0x10a9cc81c]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(fio_defer_cycle+0x60) [0x10a9e16d0]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(defer_thread_start+0x1b) [0x10aa33c8b]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(call_without_gvl+0xb0) [0x10753c420]
/Users/sshock/.rvm/gems/ruby-2.6.2@myproj/gems/iodine-0.7.31/lib/iodine/iodine.bundle(defer_thread_inGVL+0x58) [0x10aa33c58]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(thread_do_start+0x248) [0x107544868]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(thread_start_func_2+0x1f6) [0x1075442e6]
/Users/sshock/.rvm/rubies/ruby-2.6.2/lib/libruby.2.6.dylib(thread_start_func_1+0x117) [0x107543f27]
/usr/lib/system/libsystem_pthread.dylib(_pthread_body+0x7e) [0x7fff63bbf2eb]
/usr/lib/system/libsystem_pthread.dylib(0x7fff63bc2249) [0x7fff63bc2249]

Configurable cache-control headers?

Hey there!

So far, Iodine has been amazing. There one issue we have is that all of our assets are fingerprinted, so we would effectively like the browser to cache the forever. Currently, the cache-control header is set to max-age=3600, is there a way to adjust the cache-control header for static assets?

Ability to disable last-modified being set?

Is it possible to disable the automatic addition of last-modified on certain routes? we are using etags on certain controllers so we do not want to have the last-modified checked. is this possible?

Is rake-compiler a runtime dependency?

Hello,

I've noticed that rake-compiler is being a runtime dependency of this project (iodine), however, I'm not clear how that is used at runtime.

rake-compiler provides a series of tools for gem authors during development and is not aimed to be added as runtime dependency.

You can think of rake-compiler the same way you think of rake and rspec: those are tools that help you build your library, but not necessarily dependencies you want to transfer at runtime to the end user.

Could you confirm the expected need for this dependency?

Thank you. โค๏ธ โค๏ธ โค๏ธ

workers / threads available

Hi,

This is more of a question than a real issue - I'm running iodine and using it to serve many websockets. In my dev env I've noticed that even if I set processes and threads to 1 (confirmed by iodine output: Running 1 processes X 1 threads) I can still connect more than one websocket to the server. How come? Isn't there a direct correlation between number of threads and number of websockets I can connect?

Option to disable logging in test environment

Hi again, @boazsegev!
Sorry to bother you again, I will stop now!

I'm running Iodine in my spec suite and I would like to disable request logging.
I'm aware I can disable it when running in the command-line:

bundle exec rspec 2> /dev/null

However in my spec suite I'm running it with Capybara, hence I start it from the ruby interface:

# spec/support/capybara_iodine.rb
require 'rack/handler/iodine'

Capybara.register_server :iodine do |app, port, host| 
  Iodine.workers = 1
  Iodine.threads = 1
  Iodine::Rack.run(app, Port: port, Address: host)
end

Capybara.server = :iodine

RSpec.configure do |config|
  config.after(:suite) do
    Iodine.stop
  end
end

When I run bundle exec rspec (without 2> /dev/null) it outputs the following:

$ bundle exec rspec
Running via Spring preloader in process 1254

Randomized with seed 29602
INFO: Listening on port 34365
INFO: Starting up Iodine:
 * Iodine 0.7.20
 * Ruby 2.5.1
 * facil.io 0.7.0.beta8 (epoll)
 * 1 Workers X 5 Threads per worker.
 * Maximum 131072 open files / sockets per worker.
 * Master (root) process: 1244.

.Content type: application/x-www-form-urlencoded; charset=UTF-8
..Content type: application/x-www-form-urlencoded; charset=UTF-8
...

I know I can run bundle exec rspec 2> /dev/null to suppress the request logs, but I saw that there is an option to disable logging on listen, but I can't use it. I tried like this:

Capybara.register_server :iodine do |app, port, host|
  Iodine.workers = 1
  Iodine.threads = 1
  Iodine.listen(
    service: :http,
    handler: app,
    port: port,
    address: host,
    log: false
  )
  Iodine.start
end

I get the impression that the case log: false is not being treated.
Is there a way to disable logging in this case?

Thanks in advance for any hint! ๐Ÿ˜€๐Ÿ‘๐Ÿป

Error with ActionCable 5.2.0

Hello,

I don't know whether this project support Rails or not, but I guess it does since Rails is a Rack application :-) anyway, I have the following error:

#<Thread:0x00007fd6d6758cd0@/home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:75 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
	5: from /home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:75:in `block (2 levels) in spawn'
	4: from /home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:86:in `run'
	3: from /home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:86:in `loop'
	2: from /home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:93:in `block in run'
	1: from /home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:41:in `block in detach'
/home/user/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/actioncable-5.2.0/lib/action_cable/connection/stream_event_loop.rb:41:in `close': Bad file descriptor (Errno::EBADF)

It happens on a newly created Rails 5.2.0 application, Ruby 2.5.1, iodine 0.6.3, visiting any page which includes an ActionCable socket. It can be reproduced with the following steps:

  1. Create a new Rails 5.2.0
  2. Generate a scaffold (just in order to have a page to visit)
  3. Add iodine to Gemfile
  4. Run bundle exec iodine -p 3000 -t 8 -w 1 -www public -v
  5. Visit the scaffold page from the browser in order to make it try to connect to ActionCable websocket

Possible memory issue when using Iodine with Sinatra and publish in server process

Hi!

I found that the server throws strange errors without backtrace in the case when it runs with Sinatra and uses publish \ subscribe for server processes.

Video with the problem reproduction:
https://youtu.be/c5AWvsdX0AE
Repo with test code:
https://github.com/haukot/iodine_issue

Errors are similar to these:

Iodine caught an unprotected exception - NoMethodError: undefined method `call' for "ensure in block in singleton class":String
Iodine caught an unprotected exception - NotImplementedError: method `call' called on unexpected T_IMEMO object (0x000000018bd660 flags=0x405a)
Iodine caught an unprotected exception - NoMethodError: undefined method `call' for "^xml(\\s+(.+))?$":String
Iodine caught an unprotected exception - NoMethodError: undefined method `call' for #<Array:0x000000028494d0>
Iodine caught an unprotected exception - NotImplementedError: method `call' called on unexpected T_NODE object (0x0000000163d778 flags=0x35491b)
Iodine caught an unprotected exception - NoMethodError: undefined method `call' for "/home/haukot/.rvm":String

It seems that the process captures a random memory. Usually these errors don't do anything, and even messages are written to client sockets correctly. But sometimes messages not written, and once the entire server has crashed. This is error dump:
https://github.com/haukot/iodine_issue/blob/master/error_dump

In other cases (in particular, if the Slim template is not used), the server writes errors like this:

ERROR: (pub/sub) cluster message size error. Message ignored.

P.S. It would be really cool if you have any ideas how to show any backtrace in such situations, even if it will be C backtrace. Otherwise it's quite difficult to localize the problem.

iodine >0.7.1 set empty set-cookie headers, linux x86_64

With simple rack app

require 'sinatra'
require 'sinatra/cookies'

class App < Sinatra::Application # :nodoc:
  enable :sessions
  set :session_secret, 'my_secrets'

  get '/' do
    session[:user] = 'session_user'
    cookies['user'] = 'cookies_user'
    'Hello World!'
  end
end

I'm getting empty Set-Cookie headers using iodine > 0.7.1:

set-cookie:
set-cookie:

with version 0.7.1 I get correct

set-cookie: user=cookies_user; path=/; HttpOnly
set-cookie: rack.session=โ€ฆ; path=/; HttpOnly

git bisect told me that

ba5246923393d38f6e6a1be0374cd8f339f24459 is the first bad commit
commit ba5246923393d38f6e6a1be0374cd8f339f24459
Author: Bo <[email protected]>
Date:   Wed Oct 3 04:38:23 2018 -0400

    facil.io updates

:040000 040000 0f4fab699832dcd8c23da77a1a8ee81f7db2338e b37a2f166fba3d836143f7aa1dd1fb48724a25e8 M      ext

git bisect start
# good: [73734170bb243b55d8ee8702ff9e67ebef641f03] fix for older gcc (heroko)
git bisect good 73734170bb243b55d8ee8702ff9e67ebef641f03
# bad: [1ae2f261cb24d36243821086f3827cbb00e378e9] docs
git bisect bad 1ae2f261cb24d36243821086f3827cbb00e378e9
# good: [21dc101c54b7dc188a0b2860c2d388da3102d3f0] on_idle scheduling
git bisect good 21dc101c54b7dc188a0b2860c2d388da3102d3f0
# bad: [f3029950252e239f3f295a307c730bcd814fc725] caught facil.io at a bad time...
git bisect bad f3029950252e239f3f295a307c730bcd814fc725
# skip: [4172f6cb807b2aa12f22361907bcb160c34dc697] facil.io updates
git bisect skip 4172f6cb807b2aa12f22361907bcb160c34dc697
# bad: [ba5246923393d38f6e6a1be0374cd8f339f24459] facil.io updates
git bisect bad ba5246923393d38f6e6a1be0374cd8f339f24459
# first bad commit: [ba5246923393d38f6e6a1be0374cd8f339f24459] facil.io updates

Linux x86_64 here (Fedora 29), please tell me if any other info is needed.

Pidfile optional argument for server

Hi there, something useful to have is the pidfile argument to can kill the main process.

I'm currently using rails server --pid /my.pid but the problem with that is that the port and the bind/socket in the iodine initializer is ignored, and I have to put all the settings in the rails server command (rails s -b /socket -p 0 --pid /my.pid) instead of just iodine =) .

Thanks in advance.

Websockets: `each` fails under stress, causing the server to crash

When using websockets, multiple calls to each, while under stress (opening and closing multiple websocket connections (tested with over 200), cause Iodine to crash for reasons unknown.

The error reported seems to be write related... this is the relevant part of the log:

/Users/___/Ruby/plezi/iodine/bin/http-playground:41: [BUG] Segmentation fault at 0x000007bdbfef68
ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]

-- Crash Report log information --------------------------------------------
   See Crash Report log file under the one of following:                    
     * ~/Library/Logs/CrashReporter                                         
     * /Library/Logs/CrashReporter                                          
     * ~/Library/Logs/DiagnosticReports                                     
     * /Library/Logs/DiagnosticReports                                      
   for more details.                                                        
Don't forget to include the above Crash Report log file in bug reports.     

-- Control frame information -----------------------------------------------
c:0001 p:---- s:0002 e:000001 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
/Users/___/Ruby/plezi/iodine/bin/http-playground:38:in `block in on_message'
/Users/___/Ruby/plezi/iodine/bin/http-playground:41:in `echo'
/Users/___/Ruby/plezi/iodine/bin/http-playground:41:in `write'

-- Machine register context ------------------------------------------------
 rax: 0x000070000030bba8 rbx: 0x000070000030bc60 rcx: 0x00000007bdbfef00
 rdx: 0x0000000000000099 rdi: 0x00000007bdbfef00 rsi: 0x0000000000000001
 rbp: 0x000070000030bb80 rsp: 0x000070000030bb80  r8: 0x0000000000000000
  r9: 0x0000000000000000 r10: 0x00007fff99b9bf01 r11: 0x00007fff86e092b1
 r12: 0x0000000000000099 r13: 0x00000007bdbfef00 r14: 0x0000000000000099
 r15: 0x0000000000000099 rip: 0x00007fff86e060a5 rfl: 0x0000000000010206

-- C level backtrace information -------------------------------------------
0   ruby                                0x000000010b6f3624 rb_vm_bugreport + 388
1   ruby                                0x000000010b595073 rb_bug_context + 483
2   ruby                                0x000000010b6686a3 sigsegv + 83
3   libsystem_platform.dylib            0x00007fff8f2beeaa _sigtramp + 26
4   libsystem_c.dylib                   0x00007fff86e060a5 flockfile + 4
5   ???                                 0x000070000030bbf0 0x0 + 123145305504752

-- Other runtime information -----------------------------------------------

Rack Websocket SPEC implementation state?

Hi @matthewd , @evanphx , @tinco , @jeremy

This is a quick note to review the state of the specification.

The specification has been out for a while, but it seems to me that it's either underutilized or, more probably, unimplemented.

No changes have been proposed for quite a while, but I think it's because we haven't been testing it rather than because we are pleased with it.

I recently read a very troubling blog post about Websocket performance in Ruby when compared to NodeJS. It seems most Ruby websocket implementations break at ~500 concurrent clients.

The blog [has a repo with the code used].(https://github.com/hashrocket/websocket-shootout).

Iodine now fully supports the specification as far as I can tell (as well as an each extension which isn't part of the specifications) and I tested it with thousands of concurrent clients with positive results.

I would really love to hear how things have been developing on your end and see if there are any changes.

Kind regards,
Bo.

P.S.

I post this here since I'm not sure about the best group communication channel. If you want to guide me on this, or change the communication channel, please let me know.

Exceptions in on_message and on_data callbacks are suppressed

Hi,
I found that iodine doesn't handle exceptions raised in on_message and on_data callbacks.

Example app:

class MyChatroom
  def on_message(data)
    raise "SomeError"
  end
end

Iodine::Rack.app = Proc.new do |env|
  unless env['upgrade.websocket?']
    return [404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
  end

  env['upgrade.websocket'] = MyChatroom.new

  [-1, {}, []]
end

Iodine.start

When an exception raised the whole thread gets stuck and no messages are written to the log.
The messages are shown when the server stops, or when all available threads terminate (and the server crashes).

I guess it's because there is direct Ruby C API function call instead of RubyCaller call
https://github.com/boazsegev/iodine/blob/master/ext/iodine/iodine_websockets.c#L683
there should probably be something like this:

RubyCaller.call_arg(handler, iodine_on_message_func_id, 1, &buffer);

Issue parsing Last-Modifed value with Time.httpdate

Hey @boazsegev,

We are getting some errors when attempting to parse Last-Modified headers, and I think the error may be coming from iodine generated timestamps. Ruby's httpdate fails when parsing dates without two digits for the day number. I believe this may actually be a ruby bug (couldn't really tell from the spec) but it would be hugely helpful if iodine could bypass this by ensuring two digits :)

Example

Does not work

> Time.httpdate('Thu, 6 Mar 2019 05:17:22 GMT')
ArgumentError: not RFC 2616 compliant date: "Thu, 6 Mar 2019 05:17:22 GMT"

Works

> Time.httpdate('Thu, 06 Mar 2019 05:17:22 GMT')
ArgumentError: not RFC 2616 compliant date: "Thu, 6 Mar 2019 05:17:22 GMT"

Thank you so much!

Documentation: config/iodine.rb

First of all, thank you for this amazing project! ๐Ÿ˜€

I was reading the docs section Running with Rails and then I got confused in this part:

Optionally, it's possible to add a config/iodine.rb file

I wonder how should I tell Iodine to load config/iodine.rb.
I looked up the command line options and I expected to find a flag like Puma's:

bundle exec puma -C config/puma.rb
  1. Is there a flag I can pass to Iodine to load config/iodine.rb? Something like this:
bundle exec iodine -C config/iodine.rb
  1. Does it work when I call it with rails s instead?

Exception when using Fibers

Hi Bo,

This should be an interesting one. I'm not 100% sure this lies in Iodine, but I can't reproduce it with any other server.

Here's the stacktrace:

Traceback (most recent call last):
        10: from example.rb:39:in `<main>'
         9: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/rack-2.0.6/lib/rack/server.rb:148:in `start'
         8: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/rack-2.0.6/lib/rack/server.rb:297:in `start'
         7: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/iodine-0.7.27/lib/rack/handler/iodine.rb:13:in `run'
         6: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/iodine-0.7.27/lib/rack/handler/iodine.rb:13:in `start'
         5: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/roda-3.18.0/lib/roda/plugins/streaming.rb:85:in `each'
         4: from example.rb:32:in `block (3 levels) in <main>'
         3: from example.rb:32:in `copy_stream'
         2: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/down-4.6.1/lib/down/chunked_io.rb:162:in `readpartial'
         1: from /usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/down-4.6.1/lib/down/chunked_io.rb:266:in `retrieve_chunk'
/usr/home/adam/.rubies/ruby-2.6.1/lib/ruby/gems/2.6.0/gems/down-4.6.1/lib/down/chunked_io.rb:266:in `resume': fiber called across stack rewinding barrier (FiberError)

And here's a reproduction script (also available here):

require "bundler/inline"

gemfile do
  source "https://rubygems.org"

  gem "iodine", "0.7.27"
  gem "puma", "3.12.1"
  gem "down", "4.6.1"
  gem "roda"
end

module EachChunk
  def each_chunk(chunk_size = 1024)
    yield read(chunk_size) until eof?
  end
end

app = Class.new(Roda) do
  plugin :streaming

  route do |r|
    data = StringIO.new("This is the data")

    input = Down::ChunkedIO.new(
      size: data.size,
      chunks: data.extend(EachChunk).enum_for(:each_chunk),
      data: {
        content_type: "text/plain"
      }
    )

    stream do |output|
      IO.copy_stream(input, output)
    end
  end
end

server = ARGV[0] or abort "Usage: #{$0} iodine|puma"
Rack::Server.start(app: app, server: server.to_sym, :Port => 9292)

My uname

FreeBSD devmachine 12.0-RELEASE FreeBSD 12.0-RELEASE r341666 GENERIC  amd64

Moving the assignment of input into the stream block makes everything work, FYI, tho I don't believe that's a legit fix.

Cheers!

Make ctrl-c play nicely with capybara

currently when using iodine capybara-rspec, ctrl-c does not seem to be propogated properly back to rspec, so the specs will continue to run. if you press ctrl-c again, the tests will immediately stop and not inform the user which tests failed.

it would be great if iodine had the same behavior as puma here, where ctrl-c would be delegated to rspec so it would inform the user which specs failed.

Segfault with ruby-2.4

git clone [email protected]:can-i-has-a-job-plz/litecable.git
cd litecable
bundle
IODINE=1 bundle exec rspec
# => Segmentation fault (core dumped)

backtrace looks like

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f1b5d7e547d in rb_call (scope=CALL_FCALL, argv=0x352b1a0, argc=1, mid=4321, recv=28227120) at vm_eval.c:841
841         return rb_call(recv, mid, n, argv, CALL_FCALL);
[Current thread is 1 (Thread 0x7f1b4f800700 (LWP 13035))]

but I'm not really sure that it's useful (it's around 8k frames for segfaulted thread). Reproducible here on Fedora-29 x86_64 & on travis ci (some ubuntu, x86_64).

Logger is not showing.. sometimes :(

My Rails application that use Iodine is set to log with STDOUT
config.logger = ActiveSupport::Logger.new(STDOUT)
But I found whenever I use puts nothing is showing to my terminal until the next server restart.
It is because my bad config on the Iodine?

Segmentation fault

I ran the pubsub engine example in the gem directory. I opened http://localhost:3000 in Safari, opened a console, and connected a websocket, refreshed, connected again with a name, etc, like:

var ws = new WebSocket("ws://localhost:3000")
ws.send("Hello")

// refresh

var ws = new WebSocket("ws://localhost:3000/ohai")
ws.send("Hello")

It segfaulted (and I notice the logs look like output is being fragmented):

$ cd .../gems/iodine-0.6.0/examples
$ iodine pubsub_engine.ru
* Listening on port 3000

Starting up Iodine:
 * Ruby v.2.5.0
 * Iodine v.0.6.0
 * 4 Workers X 4 Threads per worker.

* 35394 is running.
Server is running 4 workers X 4 threads, press ^C to stop
* Detected capacity: 131072 open file limit
* Root pid: 35375
* 35396 is running.
* 35395 is running.
* 35397 is running.
* Subscribing to "chat" (exact match)
* Publishing to "chat": guest e...
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Publishing to "chat": guest: ...
* Publishing to "chat": guest: ...
* Publishing to "chat": guest l...
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Publishing to "chat": ohai en...
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Subscribing to "chat" (exact match)
* Publishing to "chat": ohai: H...
* Publishing to "chat": ohai: H...
^C* (35397) Detected exit signal.
* Server Detected exit signal.
* (35396) Detected exit signal.
* (35394) Detected exit signal.
* (35395) Detected exit signal.
* Stopped listening on port 3000
* 35394 cleanning up.
* 35395 cleanning up.
* Publishing to "chat": ohai le...
* Unsubscribing to "chat" (exact match)
* 35396 cleanning up.
* 35397 cleanning up.
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* Unsubscribing to "chat" (exact match)
* 35375 cleanning up.

   ---  Shutdown Complete  ---
u[BUG] Segmentation fault at 0x0000000000000378
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]

-- Crash Report log information --------------------------------------------
   See Crash Report log file under the one of following:
     * ~/Library/Logs/DiagnosticReports
     * /Library/Logs/DiagnosticReports
   for more details.
Don't forget to include the above Crash Report log file in bug reports.

-- Machine register context ------------------------------------------------
 rax: 0x0000000000000000 rbx: 0x00007f9bfd0cfc38 rcx: 0x0000000000000001
 rdx: 0x0000000000000004 rdi: 0x00007f9bfd0cfc38 rsi: 0x0000000000000005
 rbp: 0x00007ffee22803a0 rsp: 0x00007ffee2280368  r8: 0x0000000000000000
  r9: 0x00007f9c02ac2c80 r10: 0x000007f9c02acbe4 r11: 0x0000000000000001
 r12: 0x000000014e02c353 r13: 0x00007f9c02d22540 r14: 0x0000000000000001
 r15: 0x0000000000000004 rip: 0x000000010da07f3a rfl: 0x0000000000010246

-- C level backtrace information -------------------------------------------
0   ruby                                0x000000010db6be91 rb_vm_bugreport + 321
1   ruby                                0x000000010d9f0ef8 rb_bug_context + 472
2   ruby                                0x000000010dae0401 sigsegv + 81
3   libsystem_platform.dylib            0x00007fff6bd2df5a _sigtramp + 26
4   ruby                                0x000000010da07f3a rb_wb_protected_newobj_of + 10
5   iodine.bundle                       0x000000010df554ad iodine_pubsub_GIL_unsubscribe + 253
6   iodine.bundle                       0x000000010df551e6 iodine_pubsub_on_unsubscribe + 54
7   iodine.bundle                       0x000000010df58ce8 pubsub_client_destroy + 1976
8   iodine.bundle                       0x000000010df5b025 pubsub_cluster_cleanup + 37
9   iodine.bundle                       0x000000010df0a12e facil_libcleanup + 78
10  libsystem_c.dylib                   0x00007fff6bacbef4 __cxa_finalize_ranges + 358
11  libsystem_c.dylib                   0x00007fff6bacc1fe exit + 55

[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

[IMPORTANT]
Don't forget to include the Crash Report log file under
DiagnosticReports directory in bug reports.
Process:               ruby [35375]
Path:                  /usr/local/rbenv/versions/2.5.0/bin/ruby
Identifier:            ruby
Version:               0
Code Type:             X86-64 (Native)
Parent Process:        zsh [35200]
Responsible:           ruby [35375]
User ID:               501

Date/Time:             2018-05-28 20:56:28.501 +1000
OS Version:            Mac OS X 10.13.4 (17E202)
Report Version:        12
Bridge OS Version:     3.0 (14Y664)
Anonymous UUID:        A177B4BC-9199-AED2-5C3D-F58818555A40

Sleep/Wake UUID:       33E37F50-D292-4830-8F8F-A4099745D4C1

Time Awake Since Boot: 770000 seconds
Time Since Wake:       4300 seconds

System Integrity Protection: enabled

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGABRT)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000378
Exception Note:        EXC_CORPSE_NOTIFY

VM Regions Near 0x378:
--> 
    __TEXT                 000000010d97f000-000000010dbfd000 [ 2552K] r-x/rwx SM=COW  /usr/local/rbenv/versions/2.5.0/bin/ruby

Application Specific Information:
abort() called

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib        	0x00007fff6bb6fb6e __pthread_kill + 10
1   libsystem_pthread.dylib       	0x00007fff6bd3a080 pthread_kill + 333
2   libsystem_c.dylib             	0x00007fff6bacb1ae abort + 127
3   ruby                          	0x000000010d9f0d19 die + 9
4   ruby                          	0x000000010d9f0f58 rb_bug_context + 568
5   ruby                          	0x000000010dae0401 sigsegv + 81
6   libsystem_platform.dylib      	0x00007fff6bd2df5a _sigtramp + 26
7   ???                           	000000000000000000 0 + 0
8   iodine.bundle                 	0x000000010df554ad iodine_pubsub_GIL_unsubscribe + 253
9   iodine.bundle                 	0x000000010df551e6 iodine_pubsub_on_unsubscribe + 54
10  iodine.bundle                 	0x000000010df58ce8 pubsub_client_destroy + 1976
11  iodine.bundle                 	0x000000010df5b025 pubsub_cluster_cleanup + 37
12  iodine.bundle                 	0x000000010df0a12e facil_libcleanup + 78
13  libsystem_c.dylib             	0x00007fff6bacbef4 __cxa_finalize_ranges + 358
14  libsystem_c.dylib             	0x00007fff6bacc1fe exit + 55
15  libdyld.dylib                 	0x00007fff6ba1f01c start + 8

Thread 0 crashed with X86 Thread State (64-bit):
  rax: 0x0000000000000000  rbx: 0x00007fffa404c380  rcx: 0x00007f9bfd0e99e8  rdx: 0x0000000000000000
  rdi: 0x0000000000000307  rsi: 0x0000000000000006  rbp: 0x00007f9bfd0e9a20  rsp: 0x00007f9bfd0e99e8
   r8: 0x00007fffa402b048   r9: 0x0000000000000040  r10: 0x0000000000000000  r11: 0x0000000000000206
  r12: 0x0000000000000307  r13: 0x00007f9bfd0ea148  r14: 0x0000000000000006  r15: 0x000000000000002d
  rip: 0x00007fff6bb6fb6e  rfl: 0x0000000000000206  cr2: 0x00007fffa4029168
  
Logical CPU:     0
Error Code:      0x02000148
Trap Number:     133


Binary Images:
       0x10d97f000 -        0x10dbfcffb +ruby (0) <E4D8BBA2-F081-303C-9875-B645C0FEB14C> /usr/local/rbenv/versions/2.5.0/bin/ruby
       0x10dcdb000 -        0x10dd37fcf +libgmp.10.dylib (0) <7D2A1AB0-B206-3196-954C-5A0E17049998> /usr/local/opt/gmp/lib/libgmp.10.dylib
       0x10de5c000 -        0x10de5dffb +encdb.bundle (0) <DE260769-FC04-3B6E-8A58-A0844A813C8A> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/enc/encdb.bundle
       0x10de60000 -        0x10de61fff +transdb.bundle (0) <524D8862-C963-3BC5-B9CC-EEE38C21F473> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/enc/trans/transdb.bundle
       0x10de95000 -        0x10de9afff +stringio.bundle (0) <8A89D24D-FF06-3043-9222-FD63C961895A> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/stringio.bundle
       0x10dea1000 -        0x10dec0ff3 +socket.bundle (0) <7F274A2D-F253-3B89-BB52-9941DEDAD986> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/socket.bundle
       0x10df01000 -        0x10df01ffb +wait.bundle (0) <D9AE1879-F8CC-3A72-B4B9-7F236A45BFAF> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/io/wait.bundle
       0x10df04000 -        0x10df72ff3 +iodine.bundle (0) <4F398923-30E8-3CC7-9E0D-3EA730CEA2E4> /usr/local/rbenv/gems/2.5.0/gems/iodine-0.6.0/lib/iodine/iodine.bundle
       0x10df8b000 -        0x10df8eff7 +etc.bundle (0) <348D82A5-36FB-3800-AADD-8CC06850656A> /usr/local/rbenv/versions/2.5.0/lib/ruby/2.5.0/x86_64-darwin17/etc.bundle
       0x119dae000 -        0x119df89df  dyld (551.3) <AFAB4EFA-7020-34B1-BBEF-0F26C6D3CA36> /usr/lib/dyld
    0x7fff3fbfa000 -     0x7fff3fbfafff  com.apple.Accelerate (1.11 - Accelerate 1.11) <8632A9C5-19EA-3FD7-A44D-80765CC9C540> /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate
    0x7fff3fc12000 -     0x7fff40110fc3  com.apple.vImage (8.1 - ???) <A243A7EF-0C8E-3A9A-AA38-44AFD7507F00> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage
    0x7fff40111000 -     0x7fff4026bfe3  libBLAS.dylib (1211.50.2) <62C659EB-3E32-3B5F-83BF-79F5DF30D5CE> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
    0x7fff4026c000 -     0x7fff4029afef  libBNNS.dylib (38.1) <7BAEFDCA-3227-3E07-80D8-59B6370B89C6> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib
    0x7fff4029b000 -     0x7fff4065aff7  libLAPACK.dylib (1211.50.2) <40ADBA5F-8B2D-30AC-A7AD-7B17C37EE52D> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib
    0x7fff4065b000 -     0x7fff40670ff7  libLinearAlgebra.dylib (1211.50.2) <E8E0B7FD-A0B7-31E5-AF01-81781F71EBBE> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib
    0x7fff40671000 -     0x7fff40676ff3  libQuadrature.dylib (3) <3D6BF66A-55B2-3692-BAC7-DEB0C676ED29> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib
    0x7fff40677000 -     0x7fff406f7fff  libSparse.dylib (79.50.2) <0DC25CDD-F8C1-3D6E-B472-8B060708424F> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib
    0x7fff406f8000 -     0x7fff4070bfff  libSparseBLAS.dylib (1211.50.2) <722573CC-31CC-34B2-9032-E4F652A9CCFE> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib
    0x7fff4070c000 -     0x7fff408b9fc3  libvDSP.dylib (622.50.5) <40690941-CF89-3F90-A0AC-A4D200744A5D> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib
    0x7fff408ba000 -     0x7fff4096bfff  libvMisc.dylib (622.50.5) <BA2532DF-2D68-3DD0-9B59-D434BF702AA4> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib
    0x7fff4096c000 -     0x7fff4096cfff  com.apple.Accelerate.vecLib (3.11 - vecLib 3.11) <54FF3B43-E66C-3F36-B34B-A2B3B0A36502> /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib
    0x7fff41b0f000 -     0x7fff41b0ffff  com.apple.ApplicationServices (48 - 50) <8DA47D38-B07B-3AE2-B343-4579864430C6> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
    0x7fff41b10000 -     0x7fff41b76fff  com.apple.ApplicationServices.ATS (377 - 445.3) <000C4E9F-E0D9-371D-B304-83BA37460724> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS
    0x7fff41c0f000 -     0x7fff41d31fff  libFontParser.dylib (222.1.4) <FF68FAF6-70BB-3E39-A263-E17B6F5E00D0> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontParser.dylib
    0x7fff41d32000 -     0x7fff41d7cff7  libFontRegistry.dylib (221.3) <C84F7112-4764-3F4B-9FBA-4A022CF6346B> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib
    0x7fff41ebe000 -     0x7fff41ec2ff3  com.apple.ColorSyncLegacy (4.13.0 - 1) <A5FB2694-1559-34A8-A3D3-2029F68A63CA> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy
    0x7fff41f62000 -     0x7fff41fb4ffb  com.apple.HIServices (1.22 - 624) <828E189A-62F9-3961-8A89-22F508870CC5> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices
    0x7fff41fb5000 -     0x7fff41fc3fff  com.apple.LangAnalysis (1.7.0 - 1.7.0) <B65FF7E6-E9B5-34D8-8CA7-63D415A8A9A6> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis
    0x7fff41fc4000 -     0x7fff42010fff  com.apple.print.framework.PrintCore (13.4 - 503.2) <B90C67C1-0292-3CEC-885D-F1882CD104BE> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore
    0x7fff42011000 -     0x7fff4204bfff  com.apple.QD (3.12 - 404.2) <38B20AFF-9D54-3B52-A6DC-C0D71380AA5F> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD
    0x7fff4204c000 -     0x7fff42058fff  com.apple.speech.synthesis.framework (7.5.1 - 7.5.1) <84ADDF38-36F1-3D3B-B28D-8865FA10FCD7> /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis
    0x7fff42059000 -     0x7fff422e6ff7  com.apple.audio.toolbox.AudioToolbox (1.14 - 1.14) <87D81714-F167-39F5-B5E7-A7A432EDAB5B> /System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox
    0x7fff4260a000 -     0x7fff429a4ff7  com.apple.CFNetwork (897.15 - 897.15) <0C03AF39-3527-3E0D-8413-8E1B2A962F0D> /System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork
    0x7fff42ed9000 -     0x7fff42f92fff  com.apple.ColorSync (4.13.0 - 3325) <D283C285-447D-3258-A7E4-59532123B8FF> /System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync
    0x7fff4311f000 -     0x7fff431b2ff7  com.apple.audio.CoreAudio (4.3.0 - 4.3.0) <6E3F958D-79BB-3658-99AD-59F16BF756F1> /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio
    0x7fff43243000 -     0x7fff43599fef  com.apple.CoreData (120 - 851) <A2B59780-FB16-36A3-8EE0-E0EF072454E0> /System/Library/Frameworks/CoreData.framework/Versions/A/CoreData
    0x7fff4359a000 -     0x7fff4367fff7  com.apple.CoreDisplay (1.0 - 97.16) <FCFB2A8C-7A5E-314B-AA12-04D33656A0E6> /System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay
    0x7fff43680000 -     0x7fff43b21fef  com.apple.CoreFoundation (6.9 - 1452.23) <945E5C0A-86C5-336E-A64F-5BF06E78985A> /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
    0x7fff43b23000 -     0x7fff44133fef  com.apple.CoreGraphics (2.0 - 1161.10) <31C36FA0-4026-3347-93FD-71CD7287A6F0> /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics
    0x7fff44135000 -     0x7fff44424fff  com.apple.CoreImage (13.0.0 - 579.4.11) <702F8035-26FE-3C78-8587-4C9563E03CC4> /System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage
    0x7fff447ec000 -     0x7fff447ecfff  com.apple.CoreServices (822.31 - 822.31) <615919A2-AE11-3F27-9A37-FB0CFF8D36B6> /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
    0x7fff447ed000 -     0x7fff44861ffb  com.apple.AE (735.1 - 735.1) <08EBA184-20F7-3725-AEA6-C314448161C6> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE
    0x7fff44862000 -     0x7fff44b39fff  com.apple.CoreServices.CarbonCore (1178.4 - 1178.4) <0D5E19BF-18CB-3FA4-8A5F-F6C787C5EE08> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore
    0x7fff44b3a000 -     0x7fff44b6efff  com.apple.DictionaryServices (1.2 - 284.2) <6505B075-41C3-3C62-A4C3-85CE3F6825CD> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices
    0x7fff44b6f000 -     0x7fff44b77ffb  com.apple.CoreServices.FSEvents (1239.50.1 - 1239.50.1) <3637CEC7-DF0E-320E-9634-44A442925C65> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents
    0x7fff44b78000 -     0x7fff44d35ff7  com.apple.LaunchServices (822.31 - 822.31) <BDFAB0FF-C2C6-375B-9E84-E43E267E2F82> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices
    0x7fff44d36000 -     0x7fff44de6ff7  com.apple.Metadata (10.7.0 - 1191.4.13) <B5C22E70-C265-3C9F-865F-B138994A418D> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata
    0x7fff44de7000 -     0x7fff44e47fff  com.apple.CoreServices.OSServices (822.31 - 822.31) <690E3C93-8799-39FF-8663-190A49B002E3> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices
    0x7fff44e48000 -     0x7fff44eb6fff  com.apple.SearchKit (1.4.0 - 1.4.0) <3662545A-B1CF-3079-BDCD-C83855CEFEEE> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit
    0x7fff44eb7000 -     0x7fff44edbffb  com.apple.coreservices.SharedFileList (71.21 - 71.21) <7DB79D78-9925-33F8-96BA-35AB7AEB326A> /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList
    0x7fff4517c000 -     0x7fff452ccfff  com.apple.CoreText (352.0 - 578.18) <B8454115-2A4B-3585-A7A1-B47A638F2EEB> /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText
    0x7fff452cd000 -     0x7fff45307fff  com.apple.CoreVideo (1.8 - 0.0) <86CCC036-51BB-3DD1-9601-D93798BCCD0F> /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo
    0x7fff4560e000 -     0x7fff45613fff  com.apple.DiskArbitration (2.7 - 2.7) <A6450227-DD23-3100-ADBF-DB1C21E979F7> /System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration
    0x7fff457d4000 -     0x7fff45b9afff  com.apple.Foundation (6.9 - 1452.23) <E64540AD-1755-3C16-8537-552A00E92541> /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
    0x7fff45c0a000 -     0x7fff45c3afff  com.apple.GSS (4.0 - 2.0) <41087278-74AE-3FA5-8C0E-9C78EB696299> /System/Library/Frameworks/GSS.framework/Versions/A/GSS
    0x7fff45eb0000 -     0x7fff45f4bff7  com.apple.framework.IOKit (2.0.2 - 1445.50.26) <A63CAC8D-BF57-34FD-9A88-2F74546F2000> /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
    0x7fff45f4d000 -     0x7fff45f54ffb  com.apple.IOSurface (211.12 - 211.12) <392CA7DE-B365-364E-AF4A-33EC2CC48E6F> /System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface
    0x7fff45fab000 -     0x7fff46124fe7  com.apple.ImageIO.framework (3.3.0 - 1739.1) <E5E88083-26A7-3E48-8EB4-A1F04E0737CE> /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO
    0x7fff46125000 -     0x7fff46129ffb  libGIF.dylib (1739.1) <D39BE184-279C-36AC-B233-EE17CACDEDB3> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib
    0x7fff4612a000 -     0x7fff46211fef  libJP2.dylib (1739.1) <053925CD-59DB-372F-98A8-C2372FF0B899> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib
    0x7fff46212000 -     0x7fff46235ff7  libJPEG.dylib (1739.1) <E925D511-B457-3732-9B67-1A7DFB228EA4> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib
    0x7fff46511000 -     0x7fff46537feb  libPng.dylib (1739.1) <48633B0C-BE01-3D94-9D9D-A95D50466AF9> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib
    0x7fff46538000 -     0x7fff4653affb  libRadiance.dylib (1739.1) <A6F1064A-FFFC-3B06-8CBF-5204CDFC41C6> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib
    0x7fff4653b000 -     0x7fff46589fef  libTIFF.dylib (1739.1) <AD819413-29E3-3B2F-8997-2F88A82D9D83> /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib
    0x7fff47425000 -     0x7fff4743eff7  com.apple.Kerberos (3.0 - 1) <F86DCCDF-93C1-38B3-82C2-477C12E8EE6D> /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos
    0x7fff47e18000 -     0x7fff47e99ff7  com.apple.Metal (125.25 - 125.25) <B2ECA050-4C13-3EAD-A1C8-AC21EFF122F1> /System/Library/Frameworks/Metal.framework/Versions/A/Metal
    0x7fff47eb6000 -     0x7fff47ed1fff  com.apple.MetalPerformanceShaders.MPSCore (1.0 - 1) <0B4455FE-5C97-345C-B416-325EC6D88A2A> /System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSCore.framework/Versions/A/MPSCore
    0x7fff47ed2000 -     0x7fff47f41fef  com.apple.MetalPerformanceShaders.MPSImage (1.0 - 1) <87F14199-C445-34C2-90F8-57C29212483E> /System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSImage.framework/Versions/A/MPSImage
    0x7fff47f42000 -     0x7fff47f66fff  com.apple.MetalPerformanceShaders.MPSMatrix (1.0 - 1) <BD50FD9C-CE92-34AF-8663-968BF89202A0> /System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix
    0x7fff47f67000 -     0x7fff4804eff7  com.apple.MetalPerformanceShaders.MPSNeuralNetwork (1.0 - 1) <FBDDCAE6-EC6E-361F-B924-B3EBDEAC2D2F> /System/Library/Frameworks/MetalPerformanceShaders.framework/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork
    0x7fff4804f000 -     0x7fff4804fff7  com.apple.MetalPerformanceShaders.MetalPerformanceShaders (1.0 - 1) <20ECB52B-B5C2-39EA-88E3-DFEC0C3CC9FF> /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders
    0x7fff4904e000 -     0x7fff4905affb  com.apple.NetFS (6.0 - 4.0) <471DD96F-FA2E-3FE9-9746-2519A6780D1A> /System/Library/Frameworks/NetFS.framework/Versions/A/NetFS
    0x7fff4be4c000 -     0x7fff4be9afff  com.apple.opencl (2.8.15 - 2.8.15) <83ED39D0-1D39-3593-BA25-70A8A911DE71> /System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL
    0x7fff4be9b000 -     0x7fff4beb7ffb  com.apple.CFOpenDirectory (10.13 - 207.50.1) <C2715A7A-2E5C-3A21-ADB4-726F707A1294> /System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory
    0x7fff4beb8000 -     0x7fff4bec3fff  com.apple.OpenDirectory (10.13 - 207.50.1) <220FB6F2-4892-3A66-8838-C134CF657D3A> /System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory
    0x7fff4d042000 -     0x7fff4d044fff  libCVMSPluginSupport.dylib (16.5.10) <BF5D065A-A38B-3446-9418-799F598072EF> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib
    0x7fff4d045000 -     0x7fff4d04affb  libCoreFSCache.dylib (162.6.1) <879B2738-2E8A-3596-AFF8-9C3FB1B6828B> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib
    0x7fff4d04b000 -     0x7fff4d04ffff  libCoreVMClient.dylib (162.6.1) <64ED0A84-225F-39BC-BE0D-C896ACF5B50A> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib
    0x7fff4d050000 -     0x7fff4d059ff7  libGFXShared.dylib (16.5.10) <6024B1FE-ACD7-3314-B390-85972CB9B778> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib
    0x7fff4d05a000 -     0x7fff4d065fff  libGL.dylib (16.5.10) <AB8B6C73-8496-3784-83F6-27737ED03B09> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib
    0x7fff4d066000 -     0x7fff4d0a1fe7  libGLImage.dylib (16.5.10) <5B41D074-3132-3587-91B6-E441BA8C9F13> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib
    0x7fff4d20f000 -     0x7fff4d24dffb  libGLU.dylib (16.5.10) <F6844912-1B86-34DF-9FB5-A428CC7B5B18> /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib
    0x7fff4dbc5000 -     0x7fff4dbd4fff  com.apple.opengl (16.5.10 - 16.5.10) <BC4EEFE3-A09B-3998-B723-1415B995B0EE> /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL
    0x7fff4ea23000 -     0x7fff4ec6eff7  com.apple.QuartzCore (1.11 - 584.40) <ABC7F8FA-5F5A-31F4-868B-FDC4175BAFAC> /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore
    0x7fff4f4a2000 -     0x7fff4f7cafff  com.apple.security (7.0 - 58286.51.6) <7212D257-5324-3DBA-8C26-504D6B8F632A> /System/Library/Frameworks/Security.framework/Versions/A/Security
    0x7fff4f7cb000 -     0x7fff4f857ff7  com.apple.securityfoundation (6.0 - 55185.50.5) <087D601E-7813-3F9E-97EE-BC7081F520BD> /System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation
    0x7fff4f889000 -     0x7fff4f88dffb  com.apple.xpc.ServiceManagement (1.0 - 1) <5BFDB3ED-73A7-3035-A5BC-ADA6E4F74BFD> /System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement
    0x7fff4fc32000 -     0x7fff4fca2ff3  com.apple.SystemConfiguration (1.17 - 1.17) <8532B8E9-7E30-35A3-BC4A-DDE8E0614FDA> /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration
    0x7fff52b93000 -     0x7fff52c26ff7  com.apple.APFS (1.0 - 1) <D1CE52E0-45C0-30C4-B440-E2CF2F74CEE5> /System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS
    0x7fff539bc000 -     0x7fff53a04ff3  com.apple.AppleJPEG (1.0 - 1) <8DD410CB-76A1-3F22-9A9F-0491FA0CEB4A> /System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG
    0x7fff53a3f000 -     0x7fff53a67fff  com.apple.applesauce (1.0 - ???) <CCA8B094-1BCE-3AE3-A0A7-D544C818DE36> /System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce
    0x7fff54287000 -     0x7fff5428eff7  com.apple.coreservices.BackgroundTaskManagement (1.0 - 57.1) <51A41CA3-DB1D-3380-993E-99C54AEE518E> /System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement
    0x7fff55ccc000 -     0x7fff55cd5ff3  com.apple.CommonAuth (4.0 - 2.0) <4D237B25-27E5-3577-948B-073659F6D3C0> /System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth
    0x7fff567d8000 -     0x7fff567e8ff7  com.apple.CoreEmoji (1.0 - 69.3) <A4357F5C-0C38-3A61-B456-D7321EB2CEE5> /System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji
    0x7fff57f06000 -     0x7fff57f0affb  com.apple.DSExternalDisplay (3.1 - 380) <8D03D346-887A-3CE7-9483-4AD7804D1FBB> /System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay
    0x7fff58fd7000 -     0x7fff59405fff  com.apple.vision.FaceCore (3.3.2 - 3.3.2) <B574FE33-4A41-3611-9738-388EBAF03E37> /System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore
    0x7fff5c6cd000 -     0x7fff5c6d2ff7  com.apple.GPUWrangler (3.18.48 - 3.18.48) <D2BA4CFA-ED0A-3FEC-B5FF-2D6C0557334E> /System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler
    0x7fff5d448000 -     0x7fff5d457fff  com.apple.GraphVisualizer (1.0 - 5) <B993B8A2-5700-3DFC-9EB7-4CCEE8F959F1> /System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer
    0x7fff5d4da000 -     0x7fff5d54efff  com.apple.Heimdal (4.0 - 2.0) <18607D75-DB78-3CC7-947E-AC769195164C> /System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal
    0x7fff5de4a000 -     0x7fff5de51ff7  com.apple.IOAccelerator (378.18.1 - 378.18.1) <E47DDE66-1B2B-310F-A060-638CA5D86F9C> /System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator
    0x7fff5de55000 -     0x7fff5de6dfff  com.apple.IOPresentment (1.0 - 35.1) <214AD582-466F-3844-A0A4-DE0742A8B899> /System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment
    0x7fff5e4f6000 -     0x7fff5e5ebff7  com.apple.LanguageModeling (1.0 - 159.5.3) <7F0AC200-E3DD-39FB-8A95-00DD70B66A9F> /System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling
    0x7fff5e5ec000 -     0x7fff5e62efff  com.apple.Lexicon-framework (1.0 - 33.5) <DC94CF9E-1EB4-3C0E-B298-CA1190885276> /System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon
    0x7fff5e632000 -     0x7fff5e639ff7  com.apple.LinguisticData (1.0 - 238.3) <49A54649-1021-3DBD-99B8-1B2EDFFA5378> /System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData
    0x7fff5f318000 -     0x7fff5f381ff7  com.apple.gpusw.MetalTools (1.0 - 1) <B5F66CF4-BE75-3A0B-A6A0-2F22C7C259D9> /System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools
    0x7fff5f5a2000 -     0x7fff5f5ccffb  com.apple.MultitouchSupport.framework (1404.4 - 1404.4) <45374A2A-C0BC-3A70-8183-37295205CDFA> /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport
    0x7fff5f833000 -     0x7fff5f83efff  com.apple.NetAuth (6.2 - 6.2) <B3795F63-C14A-33E1-9EE6-02A2E7661321> /System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth
    0x7fff6585c000 -     0x7fff65af8fff  com.apple.SkyLight (1.600.0 - 312.50) <0CF4C608-8748-32BF-9586-A1601945F82C> /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight
    0x7fff67468000 -     0x7fff67470ff7  com.apple.TCC (1.0 - 1) <E1EB7272-FE6F-39AB-83CA-B2B5F2A88D9B> /System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC
    0x7fff6909f000 -     0x7fff690a1ffb  com.apple.loginsupport (1.0 - 1) <D1232C1B-80EA-3DF8-9466-013695D0846E> /System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport
    0x7fff69208000 -     0x7fff6923bff7  libclosured.dylib (551.3) <DC3DA678-9C40-339C-A9C6-32AB74FCC682> /usr/lib/closure/libclosured.dylib
    0x7fff692f5000 -     0x7fff6932eff7  libCRFSuite.dylib (41) <FE5EDB68-2593-3C2E-BBAF-1C52D206F296> /usr/lib/libCRFSuite.dylib
    0x7fff6932f000 -     0x7fff6933afff  libChineseTokenizer.dylib (28) <53633C9B-A3A8-36F7-A53C-432D802F4BB8> /usr/lib/libChineseTokenizer.dylib
    0x7fff693cc000 -     0x7fff693cdff3  libDiagnosticMessagesClient.dylib (104) <9712E980-76EE-3A89-AEA6-DF4BAF5C0574> /usr/lib/libDiagnosticMessagesClient.dylib
    0x7fff69404000 -     0x7fff695ceff3  libFosl_dynamic.dylib (17.8) <C58ED77A-4986-31C2-994C-34DDFB8106F0> /usr/lib/libFosl_dynamic.dylib
    0x7fff69606000 -     0x7fff69606fff  libOpenScriptingUtil.dylib (174) <610F0242-7CE5-3C86-951B-B646562694AF> /usr/lib/libOpenScriptingUtil.dylib
    0x7fff69742000 -     0x7fff69743ffb  libSystem.B.dylib (1252.50.4) <F64430FD-30A6-328D-A476-0AED41D371C6> /usr/lib/libSystem.B.dylib
    0x7fff697d6000 -     0x7fff697d6fff  libapple_crypto.dylib (109.50.14) <48BA2E76-BF2F-3522-A54E-D7FB7EAF7A57> /usr/lib/libapple_crypto.dylib
    0x7fff697d7000 -     0x7fff697edff7  libapple_nghttp2.dylib (1.24) <01402BC4-4822-3676-9C80-50D83F816424> /usr/lib/libapple_nghttp2.dylib
    0x7fff697ee000 -     0x7fff69818ff3  libarchive.2.dylib (54) <8FC28DD8-E315-3C3E-95FE-D1D2CBE49888> /usr/lib/libarchive.2.dylib
    0x7fff6989e000 -     0x7fff6989eff3  libauto.dylib (187) <A05C7900-F8C7-3E75-8D3F-909B40C19717> /usr/lib/libauto.dylib
    0x7fff6989f000 -     0x7fff69957ff3  libboringssl.dylib (109.50.14) <E6813F87-B5E4-3F7F-A725-E6A7F2BD02EC> /usr/lib/libboringssl.dylib
    0x7fff69958000 -     0x7fff69968ff3  libbsm.0.dylib (39) <6BC96A72-AFBE-34FD-91B1-748A530D8AE6> /usr/lib/libbsm.0.dylib
    0x7fff69969000 -     0x7fff69976ffb  libbz2.1.0.dylib (38) <0A5086BB-4724-3C14-979D-5AD4F26B5B45> /usr/lib/libbz2.1.0.dylib
    0x7fff69977000 -     0x7fff699cdfff  libc++.1.dylib (400.9) <7D3DACCC-3804-393C-ABC1-1A580FD00CB6> /usr/lib/libc++.1.dylib
    0x7fff699ce000 -     0x7fff699f2ff7  libc++abi.dylib (400.8.2) <EF5E37D7-11D9-3530-BE45-B986612D13E2> /usr/lib/libc++abi.dylib
    0x7fff699f4000 -     0x7fff69a04fff  libcmph.dylib (6) <A5509EE8-7E00-3224-8814-015B077A3CF5> /usr/lib/libcmph.dylib
    0x7fff69a05000 -     0x7fff69a1bfff  libcompression.dylib (47.50.1) <491784AE-CB90-3E27-9081-95C3A1211760> /usr/lib/libcompression.dylib
    0x7fff69cc6000 -     0x7fff69cdeff7  libcoretls.dylib (155.50.1) <D350052E-DC4D-3185-ADBA-BA48EDCEE955> /usr/lib/libcoretls.dylib
    0x7fff69cdf000 -     0x7fff69ce0ff3  libcoretls_cfhelpers.dylib (155.50.1) <B297F5D8-F2FE-3566-A752-E9D998B9C039> /usr/lib/libcoretls_cfhelpers.dylib
    0x7fff6a1b1000 -     0x7fff6a207ff3  libcups.2.dylib (462.2) <64864CBE-03A3-34C7-9DBB-C93601F183FD> /usr/lib/libcups.2.dylib
    0x7fff6a347000 -     0x7fff6a347fff  libenergytrace.dylib (16) <A92AB8B8-B986-3CE6-980D-D55090FEF387> /usr/lib/libenergytrace.dylib
    0x7fff6a37e000 -     0x7fff6a383ff3  libheimdal-asn1.dylib (520.50.6) <E358445A-B84E-31B5-BCCD-7E1397519D96> /usr/lib/libheimdal-asn1.dylib
    0x7fff6a3af000 -     0x7fff6a4a0ff7  libiconv.2.dylib (51.50.1) <2FEC9707-3FAF-3828-A50D-8605086D060F> /usr/lib/libiconv.2.dylib
    0x7fff6a4a1000 -     0x7fff6a6c8ffb  libicucore.A.dylib (59173.0.1) <CECAD5E5-3EFD-3AAC-AD9B-E355B2DD5E9C> /usr/lib/libicucore.A.dylib
    0x7fff6a715000 -     0x7fff6a716fff  liblangid.dylib (128) <39C39393-0D05-301D-93B2-F224FC4949AA> /usr/lib/liblangid.dylib
    0x7fff6a717000 -     0x7fff6a730ffb  liblzma.5.dylib (10) <3D419A50-961F-37D2-8A01-3DC7AB7B8D18> /usr/lib/liblzma.5.dylib
    0x7fff6a731000 -     0x7fff6a747ff7  libmarisa.dylib (9) <D6D2D55D-1D2E-3442-B152-B18803C0ABB4> /usr/lib/libmarisa.dylib
    0x7fff6a7f8000 -     0x7fff6aa20ff7  libmecabra.dylib (779.7.6) <F462F170-E872-3D09-B219-973D5E99C09F> /usr/lib/libmecabra.dylib
    0x7fff6abf8000 -     0x7fff6ad72fff  libnetwork.dylib (1229.51.2) <D64FE2A1-CBFD-3817-A701-9A0DDBD70DCF> /usr/lib/libnetwork.dylib
    0x7fff6adf1000 -     0x7fff6b1df7e7  libobjc.A.dylib (723) <DD9E5EC5-B507-3249-B700-93433E2D5EDF> /usr/lib/libobjc.A.dylib
    0x7fff6b1f2000 -     0x7fff6b1f6fff  libpam.2.dylib (22) <7B4D2CE2-1438-387A-9802-5CEEFBF26F86> /usr/lib/libpam.2.dylib
    0x7fff6b1f9000 -     0x7fff6b22dfff  libpcap.A.dylib (79.20.1) <FA13918B-A247-3181-B256-9B852C7BA316> /usr/lib/libpcap.A.dylib
    0x7fff6b2ac000 -     0x7fff6b2c8ffb  libresolv.9.dylib (65) <E8F3415B-4472-3202-8901-41FD87981DB2> /usr/lib/libresolv.9.dylib
    0x7fff6b319000 -     0x7fff6b4acff7  libsqlite3.dylib (274.8.1) <FCAD6A57-829E-3701-B16E-1833D620E0E8> /usr/lib/libsqlite3.dylib
    0x7fff6b680000 -     0x7fff6b6e0ff3  libusrtcp.dylib (1229.51.2) <3D8806D9-4BA9-35EE-BC44-F58BC2A0962D> /usr/lib/libusrtcp.dylib
    0x7fff6b6e1000 -     0x7fff6b6e4ffb  libutil.dylib (51.20.1) <216D18E5-0BAF-3EAF-A38E-F6AC37CBABD9> /usr/lib/libutil.dylib
    0x7fff6b6e5000 -     0x7fff6b6f2fff  libxar.1.dylib (400) <0316128D-3B47-3052-995D-97B4FE5491DC> /usr/lib/libxar.1.dylib
    0x7fff6b6f6000 -     0x7fff6b7ddfff  libxml2.2.dylib (31.10) <503721DB-0D8D-379E-B743-18CE59304155> /usr/lib/libxml2.2.dylib
    0x7fff6b7de000 -     0x7fff6b806fff  libxslt.1.dylib (15.12) <4A5E011D-8B29-3135-A52B-9A9070ABD752> /usr/lib/libxslt.1.dylib
    0x7fff6b807000 -     0x7fff6b819ffb  libz.1.dylib (70) <48C67CFC-940D-3857-8DAD-857774605352> /usr/lib/libz.1.dylib
    0x7fff6b8b5000 -     0x7fff6b8b9ff7  libcache.dylib (80) <092479CB-1008-3A83-BECF-E115F24D13C1> /usr/lib/system/libcache.dylib
    0x7fff6b8ba000 -     0x7fff6b8c4ff3  libcommonCrypto.dylib (60118.50.1) <029F5985-9B6E-3DCB-9B96-FD007678C6A7> /usr/lib/system/libcommonCrypto.dylib
    0x7fff6b8c5000 -     0x7fff6b8ccfff  libcompiler_rt.dylib (62) <968B8E3F-3681-3230-9D78-BB8732024F6E> /usr/lib/system/libcompiler_rt.dylib
    0x7fff6b8cd000 -     0x7fff6b8d6ffb  libcopyfile.dylib (146.50.5) <3885083D-50D8-3EEC-B481-B2E605180D7F> /usr/lib/system/libcopyfile.dylib
    0x7fff6b8d7000 -     0x7fff6b95cfff  libcorecrypto.dylib (562.50.17) <67007279-24E1-3F30-802D-A55CD5C27946> /usr/lib/system/libcorecrypto.dylib
    0x7fff6b9e4000 -     0x7fff6ba1dff7  libdispatch.dylib (913.50.12) <848EEE57-4235-3A61-9A52-C0097DD2AB5E> /usr/lib/system/libdispatch.dylib
    0x7fff6ba1e000 -     0x7fff6ba3bff7  libdyld.dylib (551.3) <CF59A5A5-288B-30E6-BD42-9056B4E4139A> /usr/lib/system/libdyld.dylib
    0x7fff6ba3c000 -     0x7fff6ba3cffb  libkeymgr.dylib (28) <E34E283E-90FA-3C59-B48E-1277CDB9CDCE> /usr/lib/system/libkeymgr.dylib
    0x7fff6ba3d000 -     0x7fff6ba49ff3  libkxld.dylib (4570.51.2) <A209B8A2-F5C3-35B1-A70B-1938F25B0ABE> /usr/lib/system/libkxld.dylib
    0x7fff6ba4a000 -     0x7fff6ba4aff7  liblaunch.dylib (1205.50.76) <4D52BB64-C568-3A36-8935-2480427EE2A2> /usr/lib/system/liblaunch.dylib
    0x7fff6ba4b000 -     0x7fff6ba4fffb  libmacho.dylib (906) <1902A611-081A-3452-B11E-EBD1B166E831> /usr/lib/system/libmacho.dylib
    0x7fff6ba50000 -     0x7fff6ba52ff3  libquarantine.dylib (86) <26C0BA22-8F93-3A07-9A4E-C8D53D2CE42E> /usr/lib/system/libquarantine.dylib
    0x7fff6ba53000 -     0x7fff6ba54ff3  libremovefile.dylib (45) <711E18B2-5BBE-3211-A916-56740C27D17A> /usr/lib/system/libremovefile.dylib
    0x7fff6ba55000 -     0x7fff6ba6cfff  libsystem_asl.dylib (356.50.1) <3B24F2D1-B578-359D-ADB2-0ED19A364C38> /usr/lib/system/libsystem_asl.dylib
    0x7fff6ba6d000 -     0x7fff6ba6dfff  libsystem_blocks.dylib (67) <17303FDF-0D2D-3963-B05E-B4DF63052D47> /usr/lib/system/libsystem_blocks.dylib
    0x7fff6ba6e000 -     0x7fff6baf7ff7  libsystem_c.dylib (1244.50.9) <1187BFE8-4576-3247-8177-481554E1F9E7> /usr/lib/system/libsystem_c.dylib
    0x7fff6baf8000 -     0x7fff6bafbffb  libsystem_configuration.dylib (963.50.8) <DF6B5287-203E-30CB-9947-78DF446C72B8> /usr/lib/system/libsystem_configuration.dylib
    0x7fff6bafc000 -     0x7fff6baffffb  libsystem_coreservices.dylib (51) <486000D3-D8CB-3BE7-8EE5-8BF380DE6DF7> /usr/lib/system/libsystem_coreservices.dylib
    0x7fff6bb00000 -     0x7fff6bb01fff  libsystem_darwin.dylib (1244.50.9) <09C21A4A-9EE0-388B-A9D9-DFF8F6758791> /usr/lib/system/libsystem_darwin.dylib
    0x7fff6bb02000 -     0x7fff6bb08ff7  libsystem_dnssd.dylib (878.50.17) <9033B909-BCF7-37EB-A040-ADE8081611D6> /usr/lib/system/libsystem_dnssd.dylib
    0x7fff6bb09000 -     0x7fff6bb52ff7  libsystem_info.dylib (517.30.1) <AB634A98-B8AA-3804-8436-38261FC8EC4D> /usr/lib/system/libsystem_info.dylib
    0x7fff6bb53000 -     0x7fff6bb79ff7  libsystem_kernel.dylib (4570.51.2) <7FF1E390-2FAC-33A7-A545-60D9BB6692D4> /usr/lib/system/libsystem_kernel.dylib
    0x7fff6bb7a000 -     0x7fff6bbc5fcb  libsystem_m.dylib (3147.50.1) <FC2E58BA-E6D5-3D2A-89CA-67F212294136> /usr/lib/system/libsystem_m.dylib
    0x7fff6bbc6000 -     0x7fff6bbe5fff  libsystem_malloc.dylib (140.50.6) <7FD43735-9DDD-300E-8C4A-F909A74BDF49> /usr/lib/system/libsystem_malloc.dylib
    0x7fff6bbe6000 -     0x7fff6bd16ff3  libsystem_network.dylib (1229.51.2) <F65ED095-8ABA-3355-93F8-B9D0505932F4> /usr/lib/system/libsystem_network.dylib
    0x7fff6bd17000 -     0x7fff6bd21ffb  libsystem_networkextension.dylib (767.50.25) <758F1414-796D-3422-83C9-92D2915A06CE> /usr/lib/system/libsystem_networkextension.dylib
    0x7fff6bd22000 -     0x7fff6bd2bff3  libsystem_notify.dylib (172) <08012EC0-2CD2-34BE-BF93-E7F56491299A> /usr/lib/system/libsystem_notify.dylib
    0x7fff6bd2c000 -     0x7fff6bd33ff7  libsystem_platform.dylib (161.50.1) <6355EE2D-5456-3CA8-A227-B96E8F1E2AF8> /usr/lib/system/libsystem_platform.dylib
    0x7fff6bd34000 -     0x7fff6bd3ffff  libsystem_pthread.dylib (301.50.1) <0E51CCBA-91F2-34E1-BF2A-FEEFD3D321E4> /usr/lib/system/libsystem_pthread.dylib
    0x7fff6bd40000 -     0x7fff6bd43fff  libsystem_sandbox.dylib (765.50.51) <B48D256E-D7DB-3D4F-BC95-34557170C7AA> /usr/lib/system/libsystem_sandbox.dylib
    0x7fff6bd44000 -     0x7fff6bd45ff3  libsystem_secinit.dylib (30) <DE8D14E8-A276-3FF8-AE13-77F7040F33C1> /usr/lib/system/libsystem_secinit.dylib
    0x7fff6bd46000 -     0x7fff6bd4dff7  libsystem_symptoms.dylib (820.50.37) <ED7350E0-B68B-374A-9AB7-DC1F195A8327> /usr/lib/system/libsystem_symptoms.dylib
    0x7fff6bd4e000 -     0x7fff6bd61fff  libsystem_trace.dylib (829.50.17) <6568D68B-1D4C-38EE-90A9-54821D6403C0> /usr/lib/system/libsystem_trace.dylib
    0x7fff6bd63000 -     0x7fff6bd68ff7  libunwind.dylib (35.3) <BEF3FB49-5604-3B5F-82B5-332B80023AC3> /usr/lib/system/libunwind.dylib
    0x7fff6bd69000 -     0x7fff6bd96fff  libxpc.dylib (1205.50.76) <25DB244E-217D-3CE0-A8F2-0C4255783B42> /usr/lib/system/libxpc.dylib

External Modification Summary:
  Calls made by other processes targeting this process:
    task_for_pid: 0
    thread_create: 0
    thread_set_state: 0
  Calls made by this process:
    task_for_pid: 0
    thread_create: 0
    thread_set_state: 0
  Calls made by all processes on this machine:
    task_for_pid: 591089
    thread_create: 0
    thread_set_state: 0

VM Region Summary:
ReadOnly portion of Libraries: Total=298.5M resident=0K(0%) swapped_out_or_unallocated=298.5M(100%)
Writable regions: Total=137.8M written=0K(0%) resident=0K(0%) swapped_out=0K(0%) unallocated=137.8M(100%)
 
                                VIRTUAL   REGION 
REGION TYPE                        SIZE    COUNT (non-coalesced) 
===========                     =======  ======= 
Activity Tracing                   256K        2 
Kernel Alloc Once                    8K        2 
MALLOC                           126.0M       24 
MALLOC guard page                   32K        6 
Stack                             8192K        2 
Stack Guard                       56.0M        2 
VM_ALLOCATE                       3592K        5 
__DATA                            14.2M      183 
__FONT_DATA                          4K        2 
__LINKEDIT                       193.4M       12 
__TEXT                           105.1M      188 
__UNICODE                          560K        2 
shared memory                       12K        4 
===========                     =======  ======= 
TOTAL                            507.1M      421 

Questions regarding memory handling and process restarts

Hi.

My C is a little rusty and I couldn't find much info on it, so I am asking.

How does iodine (or facil.io) handle memory bloat/fragmentation/pollution and restarting processes? I am running a proxy middleware written in Celluloid/Reel that fights with memory fragmentation a lot, despite doing most of the proxying in a C extension that I added. For Puma there is a worker killer that drains the connections and restarts when memory is too high (according to configuration), for Reel I implemented it myself. While I like Celluloid, it's very complex and I was considering to migrate to concurrent-ruby instead, but I don't have many web server options as it seems.

Are there any tools available in iodine that allow you to drain connections and restart the worker? Maybe even automatically? How does it handle memory management in general? I mean we are coming from C-land into Ruby-land and the GC in Ruby is still not very optimal, would it be beneficial to return to C-land? How thread safe is iodine-rack, can I run multiple parallel requests in Ruby-land?

Sorry for the amount of questions, but iodine is far too interesting :)

ExecJS fails when it races against iodine to reap child processes

As discovered by @jerryshen in Plezi issue#21, iodine conflict with coffee-script.

I have narrowed down the issue to the race between the two gems for child process reaping and opened an issue with the ExecJS team.

For now, the workaround will be to disable iodine's child reaping feature. I'm not happy with the solution, as iodine probably should reap any child process in order to eliminate any zombie processes. However, for now it is out of my hands :-/

POST request with no body results in a rack exception

Steps to reproduce:

  1. rails new proofofconcept
  2. cd proofofconcept
  3. rails s &
  4. curl -XPOST http://localhost:3000 -> '404'
  5. Kill rails server
  6. iodine &
  7. curl -XPOST http://localhost:3000 -> '500'

Results in the following exception:

ERROR: Iodine caught an unprotected exception - NoMethodError: undefined method `[]' for nil:NilClass
/usr/lib/ruby/vendor_ruby/rack/request.rb:340:in `POST'
/usr/lib/ruby/vendor_ruby/rack/method_override.rb:43:in `method_override_param'
/usr/lib/ruby/vendor_ruby/rack/method_override.rb:27:in `method_override'
/usr/lib/ruby/vendor_ruby/rack/method_override.rb:15:in `call'
/usr/lib/ruby/vendor_ruby/rack/runtime.rb:22:in `call'
/usr/share/rubygems-integration/all/gems/activesupport-5.2.2.1/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
/usr/share/rubygems-integration/all/gems/actionpack-5.2.2.1/lib/action_dispatch/middleware/executor.rb:14:in `call'
/usr/share/rubygems-integration/all/gems/actionpack-5.2.2.1/lib/action_dispatch/middleware/static.rb:127:in `call'
/usr/lib/ruby/vendor_ruby/rack/sendfile.rb:111:in `call'
/usr/share/rubygems-integration/all/gems/railties-5.2.2.1/lib/rails/engine.rb:524:in `call'
ERROR:

This seems to happen due to this line: https://github.com/rack/rack/blob/master/lib/rack/request.rb#L352 , form_vars is set to nil by get_header(RACK_INPUT).read which causes the safari fix check to throw a NoMethodError.

HTTPS and HTTP2 support

This is more of a question, but are there plans for HTTPS/HTTP2 support? I notice in your README that supporting HTTP2 was one of the reasons for starting this project, but then I only notice HTTP 1.1 support.

I was considering adding support via the http-2 gem, but then I couldn't find any information on setting up SSL. Am I missing something in how to configure that, or is SSL not supported yet?

How to debug catastrophic crash

I have encountered follow error

app_1        | 00:09:45 web.1     | [BUG] Segmentation fault at 0x0000000000000018
app_1        | 00:09:45 web.1     | ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-linux]
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | -- Control frame information -----------------------------------------------
app_1        | 00:09:45 web.1     | c:0001 p:---- s:0003 E:001f00 (none) [FINISH]
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | -- Machine register context ------------------------------------------------
app_1        | 00:09:45 web.1     |  RIP: 0x00007fb22a1cb34e RBP: 0x00007fb207fff490 RSP: 0x00007fb207ffc770
app_1        | 00:09:45 web.1     |  RAX: 0x0000000000000000 RBX: 0x0000000000000000 RCX: 0x0000000000000000
app_1        | 00:09:45 web.1     |  RDX: 0x00007fb22a636a00 RDI: 0x0000000000001200 RSI: 0x0000000000000000
app_1        | 00:09:45 web.1     |   R8: 0x00007fb22a3e1540  R9: 0x00007fb207ffc870 R10: 0x00007fb22dd31760
app_1        | 00:09:45 web.1     |  R11: 0x0000000000000000 R12: 0x00007fb22a636a10 R13: 0x0000000000001200
app_1        | 00:09:45 web.1     |  R14: 0x00007fb22b63a9f0 R15: 0x00007fb22a3e1540 EFL: 0x0000000000010206
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | -- C level backtrace information -------------------------------------------
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(rb_vm_bugreport+0x4f3) [0x7fb22e8ba893] vm_dump.c:684
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(rb_bug_context+0xd4) [0x7fb22e7378d4] error.c:506
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(sigsegv+0x3e) [0x7fb22e82d8be] signal.c:907
app_1        | 00:09:45 web.1     | /lib/x86_64-linux-gnu/libpthread.so.0 [0x7fb22e483890]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(sock_force_close+0x20e) [0x7fb22a1cb34e]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(sock_flush+0x1e8) [0x7fb22a1cb768]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(http1_response_finish+0x1d) [0x7fb22a1af0fd]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(http_response_finish+0x2b) [0x7fb22a1af96b]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1b495b]
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(rb_thread_call_with_gvl+0x1d3) [0x7fb22e86b8a3] thread.c:1525
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1bb854]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1ac910]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(http1_fio_parser_fn+0xa8) [0x7fb22a1ad598]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1acc72]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1a2fbc]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so(defer_perform+0x9d) [0x7fb22a1a1b2d]
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1a1bd1]
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(rb_thread_call_without_gvl2+0x42) [0x7fb22e866da2] thread.c:1320
app_1        | 00:09:45 web.1     | /bundle/gems/iodine-0.4.14/lib/iodine/iodine.so [0x7fb22a1bbd57]
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(thread_start_func_2+0x1b1) [0x7fb22e869131] thread.c:587
app_1        | 00:09:45 web.1     | /usr/local/lib/libruby.so.2.4(thread_start_func_1+0xd5) [0x7fb22e869bf5] thread_pthread.c:887
app_1        | 00:09:45 web.1     | /lib/x86_64-linux-gnu/libpthread.so.0 [0x7fb22e47c064]
app_1        | 00:09:45 web.1     | /lib/x86_64-linux-gnu/libc.so.6(__clone+0x6d) [0x7fb22da7562d]
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | -- Other runtime information -----------------------------------------------
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | * Loaded script: /bundle/bin/iodine
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     | * Loaded features:
app_1        | 00:09:45 web.1     | 
app_1        | 00:09:45 web.1     |     0 enumerator.so
app_1        | 00:09:45 web.1     |     1 thread.rb
app_1        | 00:09:45 web.1     |     2 rational.so
app_1        | 00:09:45 web.1     |     3 complex.so
app_1        | 00:09:45 web.1     |     4 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/encdb.so
app_1        | 00:09:45 web.1     |     5 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/trans/transdb.so
app_1        | 00:09:45 web.1     |     6 /usr/local/lib/ruby/2.4.0/unicode_normalize.rb
app_1        | 00:09:45 web.1     |     7 /usr/local/lib/ruby/2.4.0/x86_64-linux/rbconfig.rb
app_1        | 00:09:45 web.1     |     8 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/compatibility.rb
app_1        | 00:09:45 web.1     |     9 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/defaults.rb
app_1        | 00:09:45 web.1     |    10 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/deprecate.rb
app_1        | 00:09:45 web.1     |    11 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/errors.rb
app_1        | 00:09:45 web.1     |    12 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/version.rb
app_1        | 00:09:45 web.1     |    13 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/requirement.rb
app_1        | 00:09:45 web.1     |    14 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/platform.rb
app_1        | 00:09:45 web.1     |    15 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/basic_specification.rb
app_1        | 00:09:45 web.1     |    16 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/stub_specification.rb
app_1        | 00:09:45 web.1     |    17 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/util/list.rb
app_1        | 00:09:45 web.1     |    18 /usr/local/lib/ruby/2.4.0/x86_64-linux/stringio.so
app_1        | 00:09:45 web.1     |    19 /usr/local/lib/ruby/2.4.0/uri/rfc2396_parser.rb
app_1        | 00:09:45 web.1     |    20 /usr/local/lib/ruby/2.4.0/uri/rfc3986_parser.rb
app_1        | 00:09:45 web.1     |    21 /usr/local/lib/ruby/2.4.0/uri/common.rb
app_1        | 00:09:45 web.1     |    22 /usr/local/lib/ruby/2.4.0/uri/generic.rb
app_1        | 00:09:45 web.1     |    23 /usr/local/lib/ruby/2.4.0/uri/ftp.rb
app_1        | 00:09:45 web.1     |    24 /usr/local/lib/ruby/2.4.0/uri/http.rb
app_1        | 00:09:45 web.1     |    25 /usr/local/lib/ruby/2.4.0/uri/https.rb
app_1        | 00:09:45 web.1     |    26 /usr/local/lib/ruby/2.4.0/uri/ldap.rb
app_1        | 00:09:45 web.1     |    27 /usr/local/lib/ruby/2.4.0/uri/ldaps.rb
app_1        | 00:09:45 web.1     |    28 /usr/local/lib/ruby/2.4.0/uri/mailto.rb
app_1        | 00:09:45 web.1     |    29 /usr/local/lib/ruby/2.4.0/uri.rb
app_1        | 00:09:45 web.1     |    30 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/specification.rb
app_1        | 00:09:45 web.1     |    31 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/exceptions.rb
app_1        | 00:09:45 web.1     |    32 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/dependency.rb
app_1        | 00:09:45 web.1     |    33 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/core_ext/kernel_gem.rb
app_1        | 00:09:45 web.1     |    34 /usr/local/lib/ruby/2.4.0/monitor.rb
app_1        | 00:09:45 web.1     |    35 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/core_ext/kernel_require.rb
app_1        | 00:09:45 web.1     |    36 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems.rb
app_1        | 00:09:45 web.1     |    37 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/path_support.rb
app_1        | 00:09:45 web.1     |    38 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/bundler_version_finder.rb
app_1        | 00:09:45 web.1     |    39 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/version.rb
app_1        | 00:09:45 web.1     |    40 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/compatibility_guard.rb
app_1        | 00:09:45 web.1     |    41 /usr/local/lib/ruby/2.4.0/x86_64-linux/pathname.so
app_1        | 00:09:45 web.1     |    42 /usr/local/lib/ruby/2.4.0/pathname.rb
app_1        | 00:09:45 web.1     |    43 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/constants.rb
app_1        | 00:09:45 web.1     |    44 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/util.rb
app_1        | 00:09:45 web.1     |    45 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/user_interaction.rb
app_1        | 00:09:45 web.1     |    46 /usr/local/lib/ruby/2.4.0/x86_64-linux/etc.so
app_1        | 00:09:45 web.1     |    47 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/config_file.rb
app_1        | 00:09:45 web.1     |    48 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/rubygems_integration.rb
app_1        | 00:09:45 web.1     |    49 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/current_ruby.rb
app_1        | 00:09:45 web.1     |    50 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/shared_helpers.rb
app_1        | 00:09:45 web.1     |    51 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/vendor/fileutils/lib/fileutils.rb
app_1        | 00:09:45 web.1     |    52 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/vendored_fileutils.rb
app_1        | 00:09:45 web.1     |    53 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/errors.rb
app_1        | 00:09:45 web.1     |    54 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/environment_preserver.rb
app_1        | 00:09:45 web.1     |    55 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/plugin/api.rb
app_1        | 00:09:45 web.1     |    56 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/plugin.rb
app_1        | 00:09:45 web.1     |    57 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/git.rb
app_1        | 00:09:45 web.1     |    58 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/installed.rb
app_1        | 00:09:45 web.1     |    59 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/specific_file.rb
app_1        | 00:09:45 web.1     |    60 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/local.rb
app_1        | 00:09:45 web.1     |    61 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/lock.rb
app_1        | 00:09:45 web.1     |    62 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source/vendor.rb
app_1        | 00:09:45 web.1     |    63 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/source.rb
app_1        | 00:09:45 web.1     |    64 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/gem_helpers.rb
app_1        | 00:09:45 web.1     |    65 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/match_platform.rb
app_1        | 00:09:45 web.1     |    66 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/rubygems_ext.rb
app_1        | 00:09:45 web.1     |    67 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/build_metadata.rb
app_1        | 00:09:45 web.1     |    68 /usr/local/lib/ruby/site_ruby/2.4.0/bundler.rb
app_1        | 00:09:45 web.1     |    69 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/settings.rb
app_1        | 00:09:45 web.1     |    70 /usr/local/lib/ruby/site_ruby/2.4.0/rubygems/ext/builder.rb
app_1        | 00:09:45 web.1     |    71 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source.rb
app_1        | 00:09:45 web.1     |    72 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source/path.rb
app_1        | 00:09:45 web.1     |    73 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source/git.rb
app_1        | 00:09:45 web.1     |    74 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source/rubygems.rb
app_1        | 00:09:45 web.1     |    75 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/lockfile_parser.rb
app_1        | 00:09:45 web.1     |    76 /usr/local/lib/ruby/2.4.0/set.rb
app_1        | 00:09:45 web.1     |    77 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/definition.rb
app_1        | 00:09:45 web.1     |    78 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/dependency.rb
app_1        | 00:09:45 web.1     |    79 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/ruby_dsl.rb
app_1        | 00:09:45 web.1     |    80 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/dsl.rb
app_1        | 00:09:45 web.1     |    81 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source_list.rb
app_1        | 00:09:45 web.1     |    82 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source/metadata.rb
app_1        | 00:09:45 web.1     |    83 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/feature_flag.rb
app_1        | 00:09:45 web.1     |    84 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/ruby_version.rb
app_1        | 00:09:45 web.1     |    85 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/lazy_specification.rb
app_1        | 00:09:45 web.1     |    86 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/index.rb
app_1        | 00:09:45 web.1     |    87 /usr/local/lib/ruby/2.4.0/tsort.rb
app_1        | 00:09:45 web.1     |    88 /usr/local/lib/ruby/2.4.0/forwardable/impl.rb
app_1        | 00:09:45 web.1     |    89 /usr/local/lib/ruby/2.4.0/forwardable.rb
app_1        | 00:09:45 web.1     |    90 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/spec_set.rb
app_1        | 00:09:45 web.1     |    91 /usr/local/lib/ruby/2.4.0/shellwords.rb
app_1        | 00:09:45 web.1     |    92 /usr/local/lib/ruby/2.4.0/delegate.rb
app_1        | 00:09:45 web.1     |    93 /usr/local/lib/ruby/2.4.0/fileutils.rb
app_1        | 00:09:45 web.1     |    94 /usr/local/lib/ruby/2.4.0/tmpdir.rb
app_1        | 00:09:45 web.1     |    95 /usr/local/lib/ruby/2.4.0/tempfile.rb
app_1        | 00:09:45 web.1     |    96 /usr/local/lib/ruby/site_ruby/2.4.0/bundler/source/git/git_proxy.rb
app_1        | 00:09:45 web.1     |    97 /usr/local/lib/ruby/2.4.0/x86_64-linux/digest.so
app_1        | 00:09:45 web.1     |    98 /usr/local/lib/ruby/2.4.0/digest.rb
app_1        | 00:09:45 web.1     |    99 /usr/local/lib/ruby/2.4.0/x86_64-linux/digest/sha1.so

The app is totally dead and need to restart. Do you have an idea of how to better debug this kind of problem to prevent it from happening?

Compilation on Mac requires OS X 10.12?

System: mid-2011 iMac, 3.1 GHz Intel Core i5, OS X 10.11.6

Running gem install iodine fails when compiling facil.c, where the constant CLOCK_REALTIME is reported as nonexistent. The discussion on this SO question for an identically-manifesting error eventually converges on a consensus that Xcode 8 is required for that standard constant, and thus OS X 10.12 (which is required to run Xcode 8).

It Would Be Very Nice If the OS X version restriction was noted prominently in the README. It would be even nicer (though unexpected) if anyone has ideas for a workaround that would support those of us thus far unable to migrate to (High) Sierra.

Output from running gem install iodine follows:

โœ— gem install iodine                                                                     
Fetching: iodine-0.5.0.gem (100%)
Building native extensions. This could take a while...
ERROR:  Error installing iodine:
	ERROR: Failed to build gem native extension.

    current directory: /Users/jeffdickey/src/ruby/conversagence/tmp/gemset/gems/iodine-0.5.0/ext/iodine
/usr/local/Cellar/rbenv/1.1.1/versions/2.5.1/bin/ruby -r ./siteconf20180501-39307-bex283.rb extconf.rb
checking for kevent()... yes
checking for clang... yes
testing clang for stdatomic support...
using clang compiler v. 4.2.1
.
creating Makefile

current directory: /Users/jeffdickey/src/ruby/conversagence/tmp/gemset/gems/iodine-0.5.0/ext/iodine
make "DESTDIR=" clean

current directory: /Users/jeffdickey/src/ruby/conversagence/tmp/gemset/gems/iodine-0.5.0/ext/iodine
make "DESTDIR="
compiling defer.c
compiling evio.c
compiling evio_callbacks.c
compiling evio_epoll.c
compiling evio_kqueue.c
compiling facil.c
facil.c:452:3: warning: implicit declaration of function 'clock_gettime' is invalid in C99 [-Wimplicit-function-declaration]
  clock_gettime(CLOCK_REALTIME, &facil_data->last_cycle);
  ^
facil.c:452:17: error: use of undeclared identifier 'CLOCK_REALTIME'
  clock_gettime(CLOCK_REALTIME, &facil_data->last_cycle);
                ^
facil.c:1464:17: error: use of undeclared identifier 'CLOCK_REALTIME'
  clock_gettime(CLOCK_REALTIME, &facil_data->last_cycle);
                ^
facil.c:1560:17: error: use of undeclared identifier 'CLOCK_REALTIME'
  clock_gettime(CLOCK_REALTIME, &facil_data->last_cycle);
                ^
facil.c:2063:19: error: use of undeclared identifier 'CLOCK_REALTIME'
    clock_gettime(CLOCK_REALTIME, &facil_data->last_cycle);
                  ^
1 warning and 4 errors generated.
make: *** [facil.o] Error 1

make failed, exit code 2

connection update to websocket

hi,

I'm trying to update from 0.3.6 to 0.4.6. Any guides on how to do that? Our application uses WebSockets heavily. We used to upgrade our connection like so:

if env['upgrade.websocket?'.freeze] && env["HTTP_UPGRADE".freeze] =~ /websocket/i.freeze
  env['iodine.websocket'.freeze] = WebsocketHandler.new 
  [100, {"Sec-WebSocket-Protocol" => env["HTTP_SEC_WEBSOCKET_PROTOCOL"]}, []]
end

This was then translated by iodine to 101 Switching Protocols and iodine would add all the required headers for websocket update on it's own.

Sadly this doesn't work anymore, as browser reports 'unexpected response code 100'.

Examples in the readme say now I should use status 0 for the update? This, however, causes Chrome not to recognize the connection correctly anymore, and it seems iodine returns 500 error. I can set the status to 101 and do the update manually but that will require me to set all the other websocket protocol headers as well (Update, Sec-WebSocket-Accept etc).

Let me know what did I miss :)

Socket file is not created after restart

Hi again, I have this in my app to restart the server:

  Iodine.run_every(12 * 60 * 60 * 1000) do # each 12 hours
    Process.kill('SIGUSR1', Process.pid) unless Iodine.worker?
  end

And this is my config (in config/environments/production.rb)

if defined?(Iodine)
  # IODINE
  pid_file    = '/tmp/rails.pid'
  socket_file = 'tmp/sockets/server.sock'

  FileUtils.mkdir_p(Rails.root.join('tmp/sockets/'))

  Iodine::DEFAULT_SETTINGS[:port] = 0
  Iodine::DEFAULT_SETTINGS[:address] = socket_file

  Iodine.on_state(:pre_start) do
    IO.binwrite(pid_file, Process.pid)
  end
end

The problem is that when the server is restarted with USR1 sign the socket(:address) is not created again.

INFO: Listening on Unix Socket at tmp/sockets/server.sock                                                                                                                     
INFO: Starting up Iodine:                                                                                                                                                     
 * Iodine 0.7.29                                                                                                                                                              
 * Ruby 2.6.2                                                                                                                                                                 
 * facil.io 0.7.0.beta9 (epoll)                                                                                                                                               
 * 3 Workers X 5 Threads per worker.                                                                                                                                          
 * Maximum 131056 open files / sockets per worker.                                                                                                                            
 * Master (root) process: 18.                                                                                                                                                 
                                                                                                                                                                              
INFO: Server is running 3 workers X 5 threads with facil.io 0.7.0.beta9 (epoll)                                                                                               
* Detected capacity: 131056 open file limit                                                                                                                                   
* Root pid: 18                                                                                                                                                                
* Press ^C to stop                                                                                                                                                            
                                                                                                                                                                              
INFO: 21 is running.
INFO: 28 is running.
INFO: 38 is running.

# We have the socket file here.

# After a few hours
INFO: (21) detected exit signal.
INFO: (21) cleanup complete.
WARNING: Child worker (21) shutdown. Respawning worker.
INFO: (28) detected exit signal.
INFO: 77 is running.
INFO: (28) cleanup complete.
INFO: (38) detected exit signal.
INFO: (38) cleanup complete.
WARNING: Child worker (28) shutdown. Respawning worker.
WARNING: Child worker (38) shutdown. Respawning worker.
INFO: 81 is running.
INFO: 85 is running
# The socket file is not written again.

Let me know if you need more information or if i can do any workaround.

The system is running in docker ruby:2.6.2-alpine3.9

Thanks in advance ๐Ÿ‘

SSL/TLS crashes with "No supported SSL/TLS library available"

Hey GitHub Friends,

First at all, I really appreciate about your contributions on GitHub about networks.

I want to ask you a question which stacked me for a while about how to connect signalr client with ruby.

   require 'iodine'
	
   class EchoClient

   def on_open(connection)
    @messages = [ "Hello World!",
      "I'm alive and sending messages",
      "I also receive messages",
      "now that we all know this...",
      "I can stop.",
      "Goodbye." ]
    send_one_message(connection)
   end

   def boxConnStateChanged(connection, message)
    puts "Received: #{message}"
    send_one_message(connection)
   end

  def box_conn_ctate_changed(connection, message)
    puts "Received: #{message}"
    send_one_message(connection)
  end

 end


    url = "http://fbcs101.fbox360.com/push?at=#{@access_token}&cid=#{@xFboxClientId}"
    Iodine.threads = 1
    tls = Iodine::TLS.new
    Iodine.connect url: url, handler: EchoClient.new, ping: 40, service: :tls, tls: tls
    Iodine.start

here are some errors i got

FATAL: No supported SSL/TLS library available.

BIO_up_ref: symbol not found (docker)

Hi there, I'm trying to test iodine in my env, and I'm having problems in docker (my prod env). Any thoughts in what could I do about that, or if i need to install any extra lib?

Tested in => CodeFresh && locally with Docker version 18.09.0-ce, build 4d60db472b
Alpine => 3.7
Ruby => 2.5.3
iodine => (0.7.21)

Step 16/24 : RUN bundle exec rails assets:precompile
 ---> Running in 432016e7bd79
rails aborted!
LoadError: Error relocating /usr/src/app/vendor/bundle/ruby/2.5.0/gems/iodine-0.7.21/lib/iodine/iodine.so: BIO_up_ref: symbol not found - /usr/src/app/vendor/bundle/ruby/2.5.0/gems/iodine-0.7.21/lib/iodine/iodine.so
[0m[91m/usr/src/app/vendor/bundle/ruby/2.5.0/gems/iodine-0.7.21/lib/iodine.rb:5:in `require'
/usr/src/app/vendor/bundle/ruby/2.5.0/gems/iodine-0.7.21/lib/iodine.rb:5:in `<top (required)>'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:81:in `require'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:81:in `block (2 levels) in require'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:76:in `each'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:76:in `block in require'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:65:in `each'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler/runtime.rb:65:in `require'
/usr/local/bundle/gems/bundler-2.0.1/lib/bundler.rb:114:in `require'
/usr/src/app/config/application.rb:11:in `<top (required)>'
/usr/src/app/Rakefile:4:in `require'
/usr/src/app/Rakefile:4:in `<top (required)>'
/usr/src/app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/commands/rake/rake_command.rb:20:in `block in perform'
/usr/src/app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/commands/rake/rake_command.rb:18:in `perform'
/usr/src/app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/command.rb:46:in `invoke'
/usr/src/app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/commands.rb:16:in `<top (required)>'
bin/rails:4:in `require'
bin/rails:4:in `<main>'
(See full trace by running task with --trace)
Removing intermediate container 432016e7bd79
The command '/bin/sh -c bundle exec rails assets:precompile' returned a non-zero code: 1
[SYSTEM] Error: Failed to build image: caused by NonZeroExitCodeError: Container    for step title: Building Docker Image, step type: build, operation: Building image failed with exit code: 1

I tried to start up the server instead of the precompile and getting the same error.

If you need any extra data just tell me ๐Ÿ‘

listen custom HTTP method

Hey
Does it support listening to custom HTTP method?
A particular scenario requires the HTTP server part to listen to incoming custom HTTP method, specifically NOTIFY and SUBSCRIBE. Whatever data is received, that needs to be transmitted using websockets to a client, realtime.
Since it is mentioned that it can run both HTTP server and a Websocket server, how can we do this, if at all?

Feature Request: GraphQL Subscriptions Implementation

GraphQL has become a staple in the Ruby community for building APIs. GraphQL subscriptions work on top of GraphQL to add real time data streaming, and has powerful client side implementations such as Apollo.

Currently, the only FOSS implementation for graphql subscriptions in ruby is the ActionCable implementation. Obviously, there is a lot to be desired performance wise from this implementation.

I think Iodine could attract a lot of users who currently have to use the AC version. What are your thoughts on including something like this in Iodine?

Errno::EPIPE: Broken pipe @ io_writev - <STDOUT>

After updating iodine to 0.7.25, I am now seeing a broken pipe error during rails shutdown.

It's trying to do a puts "Exiting", but stdout is no longer available??

The call stack looks like this:

[GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `write`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `puts`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `puts`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `ensure in start`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `start`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:147 :in `block in perform`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:142 :in `tap`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:142 :in `perform`
 [GEM_ROOT]/gems/thor-0.20.3/lib/thor/command.rb:27 :in `run`
 [GEM_ROOT]/gems/thor-0.20.3/lib/thor/invocation.rb:126 :in `invoke_command`
 [GEM_ROOT]/gems/thor-0.20.3/lib/thor.rb:387 :in `dispatch`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/command/base.rb:65 :in `perform`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/command.rb:46 :in `invoke`
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands.rb:18 :in `<main>`
 [GEM_ROOT]/gems/bootsnap-1.4.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21 :in `require`
 [GEM_ROOT]/gems/bootsnap-1.4.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21 :in `block in require_with_bootsnap_lfi`
 [GEM_ROOT]/gems/bootsnap-1.4.1/lib/bootsnap/load_path_cache/loaded_features_index.rb:83 :in `register`
 [GEM_ROOT]/gems/bootsnap-1.4.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20 :in `require_with_bootsnap_lfi`
 [GEM_ROOT]/gems/bootsnap-1.4.1/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29 :in `require`
 [GEM_ROOT]/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:291 :in `block in require`
 [GEM_ROOT]/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:257 :in `load_dependency`
 [GEM_ROOT]/gems/activesupport-5.2.2/lib/active_support/dependencies.rb:291 :in `require`
bin/rails:4 :in `<main>`

The nested exception is:

Errno::EPIPE: Broken pipe @ io_writev - <STDOUT>
 [GEM_ROOT]/gems/railties-5.2.2/lib/rails/commands/server/server_command.rb:57 :in `write`
 SystemExit: exit
 [GEM_ROOT]/gems/rack-2.0.6/lib/rack/server.rb:293 :in `exit`

One weird thing is, that looks like an INT handler, but I'm pretty sure we are stopping rails with a TERM signal not INT, so not sure if that is a clue or not.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.