polyphony 0.43.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- metadata +482 -0
data/bin/polyphony-debug
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'polyphony'
|
7
|
+
|
8
|
+
Gyro.trace(true)
|
9
|
+
|
10
|
+
FILE_CACHE = {}
|
11
|
+
@ready = nil
|
12
|
+
@last_location = nil
|
13
|
+
@mode = :step
|
14
|
+
@current_fiber = nil
|
15
|
+
@stacks = Hash.new([])
|
16
|
+
|
17
|
+
def debug_prompt
|
18
|
+
loop do
|
19
|
+
STDOUT << '(debug) '
|
20
|
+
case (cmd = STDIN.gets.chomp)
|
21
|
+
when '.q', '.quit'
|
22
|
+
exit!
|
23
|
+
when 's', 'step'
|
24
|
+
@mode = :step
|
25
|
+
return
|
26
|
+
else
|
27
|
+
begin
|
28
|
+
result = binding.eval(cmd)
|
29
|
+
p result
|
30
|
+
rescue Exception => e
|
31
|
+
p e
|
32
|
+
puts e.backtrace.join("\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_snippet(path, lineno)
|
39
|
+
lines = FILE_CACHE[path] ||= IO.read(path).lines
|
40
|
+
start_idx = lineno - 5
|
41
|
+
stop_idx = lineno + 3
|
42
|
+
stop_idx = lines.size - 1 if stop_idx >= lines.size
|
43
|
+
start_idx = 0 if start_idx < 0
|
44
|
+
(start_idx..stop_idx).map { |idx| [idx + 1, lines[idx]]}
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_snippet(snippet, cur_line)
|
48
|
+
snippet.each do |(lineno, line)|
|
49
|
+
is_cur = lineno == cur_line
|
50
|
+
formatted = format("%s%03d %s", is_cur ? '*=> ' : ' ', lineno, line)
|
51
|
+
puts formatted
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
tp = Polyphony::Trace.new(*Polyphony::Trace::STOCK_EVENTS, :fiber_all) do |r|
|
56
|
+
unless @ready
|
57
|
+
@ready = true if r[:event] == :return
|
58
|
+
@current_fiber = r[:fiber]
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
case r[:event]
|
63
|
+
when :call, :b_call, :c_call
|
64
|
+
@stacks[r[:fiber]] << r
|
65
|
+
when :return, :b_return, :c_return
|
66
|
+
@stacks[r[:fiber]].pop
|
67
|
+
when :line
|
68
|
+
case @mode
|
69
|
+
when :step
|
70
|
+
if r[:location] != @last_location && r[:fiber] == @current_fiber
|
71
|
+
@last_location = r[:location]
|
72
|
+
puts "in #{r[:location]}"
|
73
|
+
puts
|
74
|
+
snippet = get_snippet(r[:path], r[:lineno])
|
75
|
+
print_snippet(snippet, r[:lineno])
|
76
|
+
puts
|
77
|
+
debug_prompt
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue Exception => e
|
82
|
+
p e
|
83
|
+
exit!
|
84
|
+
end
|
85
|
+
tp.enable
|
86
|
+
|
87
|
+
require File.expand_path(ARGV[0])
|
data/docs/_config.yml
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
title: "Polyphony"
|
2
|
+
description: Fine-grained concurrency for Ruby
|
3
|
+
|
4
|
+
plugins:
|
5
|
+
- jekyll-remote-theme
|
6
|
+
|
7
|
+
permalink: pretty
|
8
|
+
remote_theme: pmarsceill/just-the-docs
|
9
|
+
color_scheme: light
|
10
|
+
|
11
|
+
search_enabled: true
|
12
|
+
search:
|
13
|
+
# Split pages into sections that can be searched individually
|
14
|
+
# Supports 1 - 6, default: 2
|
15
|
+
heading_level: 2
|
16
|
+
# Maximum amount of previews per search result
|
17
|
+
# Default: 3
|
18
|
+
previews: 3
|
19
|
+
# Maximum amount of words to display before a matched word in the preview
|
20
|
+
# Default: 5
|
21
|
+
preview_words_before: 5
|
22
|
+
# Maximum amount of words to display after a matched word in the preview
|
23
|
+
# Default: 10
|
24
|
+
preview_words_after: 10
|
25
|
+
# Set the search token separator
|
26
|
+
# Default: /[\s\-/]+/
|
27
|
+
# Example: enable support for hyphenated search words
|
28
|
+
tokenizer_separator: /[\s/]+/
|
29
|
+
# Display the relative url in search results
|
30
|
+
# Supports true (default) or false
|
31
|
+
rel_url: true
|
32
|
+
# Enable or disable the search button that appears in the bottom right corner of every page
|
33
|
+
# Supports true or false (default)
|
34
|
+
button: false
|
35
|
+
|
36
|
+
aux_links:
|
37
|
+
"Polyphony on GitHub":
|
38
|
+
- "//github.com/digital-fabric/polyphony"
|
39
|
+
|
40
|
+
# Makes Aux links open in a new tab. Default is false
|
41
|
+
aux_links_new_tab: false
|
42
|
+
|
43
|
+
# Enable or disable heading anchors
|
44
|
+
heading_anchors: true
|
45
|
+
|
46
|
+
back_to_top: true
|
47
|
+
back_to_top_text: "Back to top"
|
48
|
+
|
49
|
+
footer_content: "Copyright © 2020 Sharon Rosner. Distributed by an <a href=\"https://github.com/digital-fabric/polyphony/tree/master/LICENSE\">MIT license.</a>"
|
50
|
+
|
51
|
+
# Footer "Edit this page on GitHub" link text
|
52
|
+
gh_edit_link: true # show or hide edit this page link
|
53
|
+
gh_edit_link_text: "Edit this page on GitHub"
|
54
|
+
gh_edit_repository: "https://github.com/digital-fabric/polyphony" # the github URL for your repo
|
55
|
+
gh_edit_branch: "master/docs" # the branch that your docs is served from
|
56
|
+
gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately
|
57
|
+
|
58
|
+
compress_html:
|
59
|
+
clippings: all
|
60
|
+
comments: all
|
61
|
+
endings: all
|
62
|
+
startings: []
|
63
|
+
blanklines: false
|
64
|
+
profile: false
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<head>
|
2
|
+
<meta charset="UTF-8">
|
3
|
+
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
4
|
+
|
5
|
+
{% unless site.plugins contains "jekyll-seo-tag" %}
|
6
|
+
<title>{{ page.title }} - {{ site.title }}</title>
|
7
|
+
|
8
|
+
{% if page.description %}
|
9
|
+
<meta name="Description" content="{{ page.description }}">
|
10
|
+
{% endif %}
|
11
|
+
{% endunless %}
|
12
|
+
|
13
|
+
<link rel="shortcut icon" href="{{ 'polyphony-logo.png' | absolute_url }}" type="image/png">
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
|
16
|
+
|
17
|
+
{% if site.ga_tracking != nil %}
|
18
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.ga_tracking }}"></script>
|
19
|
+
<script>
|
20
|
+
window.dataLayer = window.dataLayer || [];
|
21
|
+
function gtag(){dataLayer.push(arguments);}
|
22
|
+
gtag('js', new Date());
|
23
|
+
|
24
|
+
gtag('config', '{{ site.ga_tracking }}'{% unless site.ga_tracking_anonymize_ip == nil %}, { 'anonymize_ip': true }{% endunless %});
|
25
|
+
</script>
|
26
|
+
|
27
|
+
{% endif %}
|
28
|
+
|
29
|
+
{% if site.search_enabled != false %}
|
30
|
+
<script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | absolute_url }}"></script>
|
31
|
+
{% endif %}
|
32
|
+
<script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | absolute_url }}"></script>
|
33
|
+
|
34
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
35
|
+
|
36
|
+
{% seo %}
|
37
|
+
|
38
|
+
{% include head_custom.html %}
|
39
|
+
|
40
|
+
</head>
|
@@ -0,0 +1 @@
|
|
1
|
+
<img src="{{ 'polyphony-logo.png' | absolute_url }}" style="height: 1.5em; margin-right: 0.5em">Polyphony
|
File without changes
|
@@ -0,0 +1,126 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: All About Timers
|
4
|
+
nav_order: 1
|
5
|
+
parent: User Guide
|
6
|
+
permalink: /user-guide/all-about-timers/
|
7
|
+
---
|
8
|
+
# All About Timers
|
9
|
+
|
10
|
+
Timers form a major part of writing dynamic concurrent programs. They allow
|
11
|
+
programmers to create delays and to perform recurring operations with a
|
12
|
+
controllable frequency. Crucially, they also enable the implementation of
|
13
|
+
timeouts, which are an important aspect of concurrent programming.
|
14
|
+
|
15
|
+
## Sleeping
|
16
|
+
|
17
|
+
Sometimes, your code needs to wait for a certain period of time. For example,
|
18
|
+
implementing a retry mechanism for failed HTTP requests might involve waiting
|
19
|
+
for a few seconds before retrying. Polyphony patches the `Kernel#sleep` method
|
20
|
+
to be fiber-aware, that is to yield control of execution while waiting for a
|
21
|
+
timer to elapse.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# This is a naive retry implementation
|
25
|
+
def fetch(url)
|
26
|
+
fetch_url(url)
|
27
|
+
rescue
|
28
|
+
sleep 1
|
29
|
+
retry
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Sleeping Forever
|
34
|
+
|
35
|
+
The `#sleep` method can also be used to sleep forever, if no argument is given:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
puts "Go to sleep"
|
39
|
+
sleep
|
40
|
+
puts "Woke up" # this line will not be executed
|
41
|
+
```
|
42
|
+
|
43
|
+
The `#sleep` forever call can be used for example in the main fiber when we do
|
44
|
+
all our work in other fibers, since once the main fiber terminates the program
|
45
|
+
exits.
|
46
|
+
|
47
|
+
## Doing Work Later
|
48
|
+
|
49
|
+
While `#sleep` allows you to block execution of the current fiber, sometimes you
|
50
|
+
want to perform some work later, while not blocking the current fiber. This is done simply by spinning off another fiber:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
do_some_stuff
|
54
|
+
spin do
|
55
|
+
sleep 3
|
56
|
+
do_stuff_later
|
57
|
+
end
|
58
|
+
do_some_more_stuff
|
59
|
+
```
|
60
|
+
|
61
|
+
## Using timeouts
|
62
|
+
|
63
|
+
Polyphony provides the following global methods for using timeouts:
|
64
|
+
|
65
|
+
- `#move_on_after` - used for cancelling an operation after a certain period of time without raising an exception:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
move_on_after 1 do
|
69
|
+
sleep 60
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
This method also takes an optional return value argument:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
move_on_after 1, with_value: 'bar' do
|
77
|
+
sleep 60
|
78
|
+
'foo'
|
79
|
+
end #=> 'bar'
|
80
|
+
```
|
81
|
+
|
82
|
+
- `#cancel_after` - used for cancelling an operation after a certain period of time with a `Cancel` exception:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
cancel_after 1 do
|
86
|
+
sleep 60
|
87
|
+
end #=> raises Cancel
|
88
|
+
```
|
89
|
+
|
90
|
+
Polyphony also provides a fiber-aware version of the core Ruby `Timeout` API, which may be used directly or indirectly to interrupt blocking operations.
|
91
|
+
|
92
|
+
## Using Raw Timers
|
93
|
+
|
94
|
+
Polyphony implements timers through the `Gyro::Timer` class, which encapsulates
|
95
|
+
libev timer watchers. Using `Gyro::Timer` you can create both one-time and
|
96
|
+
recurring timers:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# Create a one-time timer
|
100
|
+
one_time = Gyro::Timer.new(1, 0)
|
101
|
+
|
102
|
+
# Create a recurring timer
|
103
|
+
recurring = Gyro::Timer.new(0.5, 1.5)
|
104
|
+
```
|
105
|
+
|
106
|
+
Once your timer is created, you can wait for it using the `#await` method:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
def delay(duration)
|
110
|
+
timer = Gyro::Timer.new(duration, 0)
|
111
|
+
timer.await
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
Waiting for the timer will *block* the current timer. This means that if you
|
116
|
+
want to do other work while waiting for the timer, you need to put it on a
|
117
|
+
different fiber:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
timer = Gyro::Timer.new(3, 0)
|
121
|
+
spin {
|
122
|
+
sleep 3
|
123
|
+
do_something_else
|
124
|
+
}
|
125
|
+
do_blocking_operation
|
126
|
+
```
|
@@ -0,0 +1,136 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Web Server
|
4
|
+
nav_order: 5
|
5
|
+
parent: User Guide
|
6
|
+
permalink: /user-guide/web-server/
|
7
|
+
---
|
8
|
+
# Web Server
|
9
|
+
|
10
|
+
Polyphony's web server functionality offers a powerful and flexible way to
|
11
|
+
create Ruby-based web servers and web applications. In addition to supporting
|
12
|
+
both HTTP 1 and HTTP 2, it supports seamless Websocket upgrades (and indeed
|
13
|
+
arbitrary protocol upgrade), TLS termination, and automatic ALPN-based protocol
|
14
|
+
selection. In addition, it includes a Rack adapter for running Rack
|
15
|
+
applications. Polyphony's web server offers excellent performance
|
16
|
+
characteristics, in terms of throughput, memory consumption and scalability
|
17
|
+
(benchmarks are forthcoming).
|
18
|
+
|
19
|
+
What makes Polyphony's web server design stand out is the fact that incoming
|
20
|
+
requests can be processed immediately upon receiving the complete headers,
|
21
|
+
without needing to wait for the entire request body to be received. This design
|
22
|
+
allows web applications to properly buffer uploads of large files without
|
23
|
+
consuming large amounts of RAM, as well as reject requests without waiting for
|
24
|
+
the entire request body.
|
25
|
+
|
26
|
+
## A basic web server
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'tipi'
|
30
|
+
|
31
|
+
Tipi.serve('0.0.0.0', 1234) do |request|
|
32
|
+
request.respond("Hello world!\n")
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Note that requests are handled using a callback block which takes a single
|
37
|
+
argument. The `request` object provides the entire API for responding to the
|
38
|
+
client.
|
39
|
+
|
40
|
+
Each client connection will be handled in a separate fiber, allowing
|
41
|
+
concurrent processing of incoming requests.
|
42
|
+
|
43
|
+
## HTTP 2 support
|
44
|
+
|
45
|
+
HTTP 2 support is baked in to the server, which supports both HTTP 2 upgrades
|
46
|
+
(for example on a non-encrypted connection) and ALPN-based protocol selection,
|
47
|
+
in a completely effortless manner.
|
48
|
+
|
49
|
+
Since HTTP 2 connections are multiplexed, allowing multiple concurrent requests
|
50
|
+
on a single connection, each HTTP 2 stream is handled in a separate fiber.
|
51
|
+
|
52
|
+
## TLS termination
|
53
|
+
|
54
|
+
TLS termination can be handled by passing a `secure_context` option to the
|
55
|
+
server:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require 'tipi'
|
59
|
+
require 'localhost/authority'
|
60
|
+
|
61
|
+
authority = Localhost::Authority.fetch
|
62
|
+
opts = { secure_context: authority.server_context }
|
63
|
+
|
64
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
65
|
+
request.respond("Hello world!\n")
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## Websockets && HTTP upgrades
|
70
|
+
|
71
|
+
Polyphony's web server makes it really easy to integrate websocket communication
|
72
|
+
with normal HTTP processing:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
require 'tipi'
|
76
|
+
require 'tipi/websocket'
|
77
|
+
|
78
|
+
ws_handler = Polyphony::Websocket.handler do |ws|
|
79
|
+
while (msg = ws.recv)
|
80
|
+
ws << "you said: #{msg}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
opts = {
|
85
|
+
upgrade: { websocket: ws_handler }
|
86
|
+
}
|
87
|
+
|
88
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
89
|
+
request.respond("Hello world!\n")
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Polyphony also supports general-purpose HTTP upgrades using the same mechanism:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
require 'tipi'
|
97
|
+
|
98
|
+
opts = {
|
99
|
+
upgrade: {
|
100
|
+
echo: ->(conn) {
|
101
|
+
while (msg = conn.gets)
|
102
|
+
conn << "You said: #{msg}"
|
103
|
+
end
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |request|
|
109
|
+
request.respond("Hello world!\n")
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## Sending HTTP responses
|
114
|
+
|
115
|
+
The response API provides multiple ways of responding, with or without a body,
|
116
|
+
and enables streaming (using chunked encoding for HTTP/1.1 connections). Here's
|
117
|
+
an example of an SSE response:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
require 'tipi'
|
121
|
+
|
122
|
+
def sse_response(request)
|
123
|
+
request.send_headers('Content-Type': 'text/event-stream')
|
124
|
+
move_on_after(10) {
|
125
|
+
loop {
|
126
|
+
request.send_chunk("data: #{Time.now}\n\n")
|
127
|
+
sleep 1
|
128
|
+
}
|
129
|
+
}
|
130
|
+
ensure
|
131
|
+
request.send_chunk("retry: 0\n\n", done: true)
|
132
|
+
end
|
133
|
+
|
134
|
+
Tipi.serve('0.0.0.0', 1234, &method(:sse_response))
|
135
|
+
```
|
136
|
+
|