• Stars
    star
    775
  • Rank 58,632 (Top 2 %)
  • Language
    Elixir
  • License
    MIT License
  • Created about 5 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Thousand Island is a pure Elixir socket server

Thousand Island Thousand Island

Build Status Docs Hex.pm

Thousand Island is a modern, pure Elixir socket server, inspired heavily by ranch. It aims to be easy to understand and reason about, while also being at least as stable and performant as alternatives. Informal tests place ranch and Thousand Island at roughly the same level of performance and overhead; short of synthetic scenarios on the busiest of servers, they perform equally for all intents and purposes.

Thousand Island is written entirely in Elixir, and is nearly dependency-free (the only library used is telemetry). The application strongly embraces OTP design principles, and emphasizes readable, simple code. The hope is that as much as Thousand Island is capable of backing the most demanding of services, it is also useful as a simple and approachable reference for idiomatic OTP design patterns.

Usage

Thousand Island is implemented as a supervision tree which is intended to be hosted inside a host application, often as a dependency embedded within a higher-level protocol library such as Bandit. Aside from supervising the Thousand Island process tree, applications interact with Thousand Island primarily via the ThousandIsland.Handler behaviour.

Handlers

The ThousandIsland.Handler behaviour defines the interface that Thousand Island uses to pass ThousandIsland.Sockets up to the application level; together they form the primary interface that most applications will have with Thousand Island. Thousand Island comes with a few simple protocol handlers to serve as examples; these can be found in the examples folder of this project. A simple implementation would look like this:

defmodule Echo do
  use ThousandIsland.Handler

  @impl ThousandIsland.Handler
  def handle_data(data, socket, state) do
    ThousandIsland.Socket.send(socket, data)
    {:continue, state}
  end
end

{:ok, pid} = ThousandIsland.start_link(port: 1234, handler_module: Echo)

For more information, please consult the ThousandIsland.Handler documentation.

Starting a Thousand Island Server

Thousand Island servers exist as a supervision tree, and are started by a call to ThousandIsland.start_link/1. There are a number of options supported; for a complete description, consult the Thousand Island docs.

Connection Draining & Shutdown

The ThousandIsland.Server process is just a standard Supervisor, so all the usual rules regarding shutdown and shutdown timeouts apply. Immediately upon beginning the shutdown sequence the ThousandIsland.ShutdownListener will cause the listening socket to shut down, which in turn will cause all of the Acceptor processes to shut down as well. At this point all that is left in the supervision tree are several layers of Supervisors and whatever Handler processes were in progress when shutdown was initiated. At this point, standard Supervisor shutdown timeout semantics give existing connections a chance to finish things up. Handler processes trap exit, so they continue running beyond shutdown until they either complete or are :brutal_killed after their shutdown timeout expires.

The shutdown_timeout configuration option allows for fine grained control of the shutdown timeout value. It defaults to 15000 ms.

Logging & Telemetry

As a low-level library, Thousand Island purposely does not do any inline logging of any kind. The ThousandIsland.Logger module defines a number of functions to aid in tracing connections at various log levels, and such logging can be dynamically enabled and disabled against an already running server. This logging is backed by telemetry events internally.

Thousand Island emits a rich set of telemetry events including spans for each server, acceptor process, and individual client connection. These telemetry events are documented in the ThousandIsland.Telemetry module.

Implementation Notes

At a top-level, a Server coordinates the processes involved in responding to connections on a socket. A Server manages two top-level processes: a Listener which is responsible for actually binding to the port and managing the resultant listener socket, and an AcceptorPoolSupervisor which is responsible for managing a pool of AcceptorSupervisor processes.

Each AcceptorSupervisor process (there are 100 by default) manages two processes: an Acceptor which accepts connections made to the server's listener socket, and a DynamicSupervisor which supervises the processes backing individual client connections. Every time a client connects to the server's port, one of the Acceptors receives the connection in the form of a socket. It then creates a new process based on the configured handler to manage this connection, and immediately waits for another connection. It is worth noting that Acceptor processes are long-lived, and normally live for the entire period that the Server is running.

A handler process is tied to the lifecycle of a client connection, and is only started when a client connects. The length of its lifetime beyond that of the underlying connection is dependent on the behaviour of the configured Handler module. In typical cases its lifetime is directly related to that of the underlying connection.

This hierarchical approach reduces the time connections spend waiting to be accepted, and also reduces contention for DynamicSupervisor access when creating new Handler processes. Each AcceptorSupervisor subtree functions nearly autonomously, improving scalability and crash resiliency.

Graphically, this shakes out like so:

graph TD;
  Server(Server: supervisor, rest_for_one)-->Listener;
  Server-->AcceptorPoolSupervisor(AcceptorPoolSupervisor: dynamic supervisor);
  AcceptorPoolSupervisor--1...n-->AcceptorSupervisor(AcceptorSupervisor: supervisor, rest_for_one)
  AcceptorSupervisor-->DynamicSupervisor
  AcceptorSupervisor-->Acceptor(Acceptor: task)
  DynamicSupervisor--1...n-->Handler(Handler: gen_server)
  Server-->ShutdownListener;

Thousand Island does not use named processes or other 'global' state internally (other than telemetry event names). It is completely supported for a single node to host any number of Server processes each listening on a different port.

Contributing

Contributions to Thousand Island are very much welcome! Before undertaking any substantial work, please open an issue on the project to discuss ideas and planned approaches so we can ensure we keep progress moving in the same direction.

All contributors must agree and adhere to the project's Code of Conduct.

Security disclosures should be handled per Thousand Island's published security policy.

Installation

Thousand Island is available in Hex. The package can be installed by adding thousand_island to your list of dependencies in mix.exs:

def deps do
  [
    {:thousand_island, "~> 1.0-pre"}
  ]
end

Documentation can be found at https://hexdocs.pm/thousand_island.

License

MIT

More Repositories

1

bandit

Bandit is a pure Elixir HTTP server for Plug & WebSock applications
Elixir
1,634
star
2

hap

A HomeKit Accessory Protocol (HAP) Implementation for Elixir
Elixir
74
star
3

machete

Literate test matchers for ExUnit
Elixir
43
star
4

MTStackableNavigationController

A drop-in replacement for UINavigationController with stacked views ala Path / Facebook.
Objective-C
39
star
5

beats

Beats is a drum machine
Elixir
38
star
6

pragmatic_context

A library to declaratively add JSON-LD support to your ActiveModel objects
Ruby
29
star
7

boiler

11
star
8

MTMultipleViewController

A container view controller that allows the selection of child views based on a UISegmentedControl in the navigation bar
Objective-C
11
star
9

hawk

A simple iOS ad-hoc distribution tool, backed by S3 and good old fashioned email
Ruby
9
star
10

desk_clock

A Nerves-powered, NTP-synchronized, SPI-connected, OLED-outputting desktop clock
Elixir
8
star
11

man_or_machine

A handy-dandy Adhearsion component that detects an answering machine at the far end of a call and facilitates differing behaviours as a result
Ruby
8
star
12

talks

Source for various talks I've given
Elixir
8
star
13

elixir-ci-actions

Shared GitHub Actions workflows for Elixir CI pipelines
7
star
14

doo

Doo - a relentlessly polymorphic approach to deployment scripting
Ruby
6
star
15

MTCollectionOperators

Objective-C
5
star
16

minicap

Minicap replaces the core bits of the stock capistrano recipes with a tiny git-focused implementation
Ruby
5
star
17

Prefer

The simple preference setter for OS X
Objective-C
4
star
18

rainmaker

Rainmaker is a pragmatic UEC (Ubuntu Elastic Cloud) manager
Ruby
4
star
19

benchmark

Benchmarking software for Plug servers
Elixir
3
star
20

MTGenericSemimodalSegue

Objective-C
3
star
21

MTTextViewController

Objective-C
3
star
22

Jumpcut

A minimalist clipboard buffer for OS X
Objective-C
3
star
23

easel

Easel gets your model cozy with RDF
Ruby
3
star
24

network_benchmark

Elixir
3
star
25

hap.rs

Rust
3
star
26

menutree

a simple hierachical command line shell
Ruby
3
star
27

ex_paint

A simple 2D rasterizer for Elixir
Elixir
2
star
28

basicboxen

Basic boxen is a simple starting set of recipes and sensible defaults to build servers using chef-solo
Ruby
2
star
29

filewall

git + capistrano + shorewall = filewall. it's what you want
Ruby
2
star
30

matchat

The simplest chatroom
Ruby
2
star
31

RelayrAce

RelayrAce is a quick and dirty rubygem to talk to the Canakit (and sparkfun) USB controller relay kit
Ruby
2
star
32

doo-extras

A set of base Ubuntu recipes for doo
Ruby
1
star
33

upstairsbox

Elixir
1
star
34

ssd1322

Elixir library for controlling SSD1322 based OLED displays
Elixir
1
star
35

dotfiles

My simple zsh / Neovim / asdf / Elixir / Ruby / macOS focused dotfiles
Shell
1
star
36

mox_demo

A test-driven introduction to the Mox library
Elixir
1
star
37

hap_demo

A demo app for HAP, the Elixir HomeKit Accessory Protocol library
Elixir
1
star
38

rpi-breakout

A simple Raspberry Pi pHAT to break out common interfaces & GPIO
1
star
39

vimfiles

My vim files
Vim Script
1
star
40

pagerduty_user_demo

Elixir
1
star
41

Smart-Keywords

Firefox & Chrome style Smart Keywords for Safari
1
star
42

heretic

Heretic is a file-based semi-structured document library
1
star
43

Former

Former is a wrapper around a minimal web interface meant to provide interstitial web based interfaces to command line driven programs
1
star
44

iolist_test

Elixir
1
star
45

pibox

Docker recipes for the rPi that runs our house
Go
1
star