Add week 4 materials
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
## 4. Групповые взаимодействия, рассылка и распространение информации.
|
||||
|
||||
### Лекция
|
||||
|
||||
- [Презентация](04-group-comm.pdf)
|
||||
- См. также материалы в конце
|
||||
|
||||
### Семинар
|
||||
|
||||
- [Материалы семинара](seminar/readme.md)
|
||||
@@ -0,0 +1,42 @@
|
||||
import random
|
||||
|
||||
from anysystem import Context, Message, Process
|
||||
|
||||
|
||||
class Peer(Process):
|
||||
def __init__(self, proc_id: int, proc_count: int, fanout: int):
|
||||
self._id = proc_id
|
||||
self._proc_count = proc_count
|
||||
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
|
||||
self._fanout = fanout
|
||||
self._info = None
|
||||
|
||||
def on_local_message(self, msg: Message, ctx: Context):
|
||||
if msg.type == 'START':
|
||||
ctx.set_timer("gossip", 1)
|
||||
elif msg.type == 'BROADCAST':
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_start(self, ctx: Context):
|
||||
pass
|
||||
|
||||
def on_message(self, msg: Message, sender: str, ctx: Context):
|
||||
if msg.type == 'GOSSIP_REQ' and self._info is not None:
|
||||
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
|
||||
elif msg.type == 'GOSSIP_RESP' and self._info is None:
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_timer(self, timer_name: str, ctx: Context):
|
||||
if self._info is None:
|
||||
self.gossip(ctx)
|
||||
ctx.set_timer("gossip", 1)
|
||||
|
||||
def got_info(self, info, ctx):
|
||||
self._info = info
|
||||
ctx.send_local(Message('DELIVER', {'info': self._info}))
|
||||
ctx.cancel_timer("gossip")
|
||||
ctx.send_local(Message('STOPPED', {}))
|
||||
|
||||
def gossip(self, ctx):
|
||||
for peer in random.sample(self._peers, self._fanout):
|
||||
ctx.send(Message('GOSSIP_REQ', {}), str(peer))
|
||||
@@ -0,0 +1,38 @@
|
||||
import random
|
||||
|
||||
from anysystem import Context, Message, Process
|
||||
|
||||
|
||||
class Peer(Process):
|
||||
def __init__(self, proc_id: int, proc_count: int, fanout: int):
|
||||
self._id = proc_id
|
||||
self._proc_count = proc_count
|
||||
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
|
||||
self._fanout = fanout
|
||||
self._info = None
|
||||
|
||||
def on_local_message(self, msg: Message, ctx: Context):
|
||||
if msg.type == 'START':
|
||||
ctx.set_timer("gossip", 1)
|
||||
elif msg.type == 'BROADCAST':
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_start(self, ctx: Context):
|
||||
pass
|
||||
|
||||
def on_message(self, msg: Message, sender: str, ctx: Context):
|
||||
if msg.type == 'GOSSIP' and self._info is None:
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_timer(self, timer_name: str, ctx: Context):
|
||||
if self._info is not None:
|
||||
self.gossip(ctx)
|
||||
ctx.set_timer("gossip", 1)
|
||||
|
||||
def got_info(self, info, ctx):
|
||||
self._info = info
|
||||
ctx.send_local(Message('DELIVER', {'info': self._info}))
|
||||
|
||||
def gossip(self, ctx):
|
||||
for peer in random.sample(self._peers, self._fanout):
|
||||
ctx.send(Message('GOSSIP', {'info': self._info}), str(peer))
|
||||
@@ -0,0 +1,43 @@
|
||||
import random
|
||||
|
||||
from anysystem import Context, Message, Process
|
||||
|
||||
|
||||
class Peer(Process):
|
||||
def __init__(self, proc_id: int, proc_count: int, fanout: int):
|
||||
self._id = proc_id
|
||||
self._proc_count = proc_count
|
||||
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
|
||||
self._fanout = fanout
|
||||
self._info = None
|
||||
|
||||
def on_local_message(self, msg: Message, ctx: Context):
|
||||
if msg.type == 'START':
|
||||
ctx.set_timer("gossip", 1)
|
||||
elif msg.type == 'BROADCAST':
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_start(self, ctx: Context):
|
||||
pass
|
||||
|
||||
def on_message(self, msg: Message, sender: str, ctx: Context):
|
||||
if msg.type == 'GOSSIP_REQ':
|
||||
if self._info is not None:
|
||||
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
|
||||
else:
|
||||
if msg['info'] is not None:
|
||||
self.got_info(msg['info'], ctx)
|
||||
elif msg.type == 'GOSSIP_RESP' and self._info is None:
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_timer(self, timer_name: str, ctx: Context):
|
||||
self.gossip(ctx)
|
||||
ctx.set_timer("gossip", 1)
|
||||
|
||||
def got_info(self, info, ctx):
|
||||
self._info = info
|
||||
ctx.send_local(Message('DELIVER', {'info': self._info}))
|
||||
|
||||
def gossip(self, ctx):
|
||||
for peer in random.sample(self._peers, self._fanout):
|
||||
ctx.send(Message('GOSSIP_REQ', {'info': self._info}), str(peer))
|
||||
@@ -0,0 +1,54 @@
|
||||
import random
|
||||
|
||||
from anysystem import Context, Message, Process
|
||||
|
||||
|
||||
class Peer(Process):
|
||||
def __init__(self, proc_id: int, proc_count: int, fanout: int):
|
||||
self._id = proc_id
|
||||
self._proc_count = proc_count
|
||||
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
|
||||
self._fanout = fanout
|
||||
self._info = None
|
||||
self._stop_prob = 0.8
|
||||
self._stopped = False
|
||||
|
||||
def on_local_message(self, msg: Message, ctx: Context):
|
||||
if msg.type == 'START':
|
||||
ctx.set_timer("gossip", 1)
|
||||
elif msg.type == 'BROADCAST':
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_start(self, ctx: Context):
|
||||
pass
|
||||
|
||||
def on_message(self, msg: Message, sender: str, ctx: Context):
|
||||
if msg.type == 'GOSSIP_REQ':
|
||||
if self._info is not None:
|
||||
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
|
||||
elif msg['info'] is not None:
|
||||
self.got_info(msg['info'], ctx)
|
||||
elif msg.type == 'GOSSIP_RESP':
|
||||
self.got_info(msg['info'], ctx)
|
||||
|
||||
def on_timer(self, timer_name: str, ctx: Context):
|
||||
self.gossip(ctx)
|
||||
ctx.set_timer("gossip", 1)
|
||||
|
||||
def got_info(self, info, ctx):
|
||||
if self._info is None:
|
||||
self._info = info
|
||||
ctx.send_local(Message('DELIVER', {'info': self._info}))
|
||||
else:
|
||||
self.try_stop(ctx)
|
||||
|
||||
def gossip(self, ctx):
|
||||
for peer in random.sample(self._peers, self._fanout):
|
||||
ctx.send(Message('GOSSIP_REQ', {'info': self._info}), str(peer))
|
||||
|
||||
def try_stop(self, ctx):
|
||||
if not self._stopped and random.uniform(0, 1) < self._stop_prob:
|
||||
self._stopped = True
|
||||
# Stop initiating exchanges; keep answering incoming requests.
|
||||
ctx.cancel_timer("gossip")
|
||||
ctx.send_local(Message('STOPPED', {}))
|
||||
@@ -0,0 +1,129 @@
|
||||
# Знакомство с gossip
|
||||
|
||||
В этой работе сравним push, pull и push-pull в симуляторе на [AnySystem](https://github.com/systems-group/anysystem). Перед каждым экспериментом запишите прогноз, затем сравните его с результатом и объясните различия.
|
||||
|
||||
## Подготовка
|
||||
|
||||
Откройте корень репозитория курса в IDE. Для VS Code с Pylance [стаб AnySystem](../../../../typings/anysystem/__init__.pyi) подключается через [настройки проекта](../../../../pyrightconfig.json). Для другой IDE укажите папку `typings` в корне репозитория как каталог стабов. Стаб содержит объявления и документацию API; реализация доступна в [исходнике AnySystem 0.3.0](https://github.com/osukhoroslov/anysystem/blob/v0.3.0/python/anysystem.py).
|
||||
|
||||
При запуске симулятора локально или в Docker Python-модуль встроен в AnySystem. Отдельный `anysystem.py` и настройка `PYTHONPATH` не нужны.
|
||||
|
||||
Из корня репозитория перейдите в каталог лабораторной:
|
||||
|
||||
```sh
|
||||
cd materials/04-group-comm/seminar/gossip
|
||||
```
|
||||
|
||||
Если установлен Rust, соберите текущую версию симулятора:
|
||||
|
||||
```sh
|
||||
cargo install --locked --path simulator
|
||||
distsys-gossip -h
|
||||
```
|
||||
|
||||
Для запуска в Docker соберите образ из текущего кода:
|
||||
|
||||
```sh
|
||||
docker build -t distsys-gossip-seminar simulator
|
||||
docker run --rm -t -v ./:/impl distsys-gossip-seminar -h
|
||||
```
|
||||
|
||||
Далее заменяйте `distsys-gossip` на `docker run --rm -t -v ./:/impl distsys-gossip-seminar`. Команды выполняются из каталога `gossip`. Если оболочка не передаёт относительный путь монтирования, в PowerShell используйте `--mount "type=bind,source=$($PWD.Path),target=/impl"` вместо `-v ./:/impl`.
|
||||
|
||||
## Модель и метрики
|
||||
|
||||
Распространяется одно сообщение, изначально известное процессу `0`. Все узлы знают состав группы и выбирают случайных соседей, исключая себя. Сетевая задержка равна 0,1 единицы модельного времени, периодический таймер срабатывает через 1. В этих экспериментах узлы не отказывают; можно задавать вероятность потери сетевого сообщения.
|
||||
|
||||
Основные параметры:
|
||||
|
||||
| Параметр | Значение |
|
||||
| --- | --- |
|
||||
| `-i` | Файл реализации |
|
||||
| `-n` | Число узлов, не меньше 2 |
|
||||
| `-f` | Число соседей для одного обмена, от 1 до `n - 1` |
|
||||
| `-d` | Вероятность потери сообщения, от 0 до 1 |
|
||||
| `-s` | Seed для воспроизведения запуска |
|
||||
| `-t` | Положительный лимит модельного времени |
|
||||
| `-q` | Завершить симуляцию при обнаружении полного охвата |
|
||||
|
||||
В таблице вывода `time` — модельное время, `delivered` — число узлов, доставивших сообщение приложению, `stopped` — число узлов, прекративших периодические инициативные обмены, `messages` — накопленное число сетевых отправлений, включая запросы и ответы. `STOPPED` не означает выключение узла: он сохраняет информацию и может отвечать на входящие запросы.
|
||||
|
||||
Симулятор выводит статистику после продвижения времени интервалами по 1. В итоговой строке `First full-coverage sample` указаны время первой напечатанной строки с `delivered = n` и число отправлений к ней. Обозначим их `t_all` и `M_all`. Это показатели с дискретностью вывода, а не точные мгновение последней доставки и число отправлений в это мгновение.
|
||||
|
||||
Строка `Finished` объясняет завершение:
|
||||
|
||||
- `full coverage (--quick-mode)` — сработало условие `-q`;
|
||||
- `no pending events` — в симуляции не осталось событий;
|
||||
- `time limit reached` — достигнут лимит времени.
|
||||
|
||||
Если условия совпали, отсутствие событий проверяется первым, затем полный охват с `-q`, затем лимит. Строка `Final state` всегда показывает фактический охват и трафик к завершению. Если полного охвата не было, выводится `Full coverage: not reached`. Завершение по лимиту не означает, что алгоритм сам остановился.
|
||||
|
||||
## 1. Сравнение push, pull и push-pull
|
||||
|
||||
Изучите [push.py](push.py), [pull.py](pull.py) и [push_pull.py](push_pull.py). В push информированный узел посылает данные соседям. В pull неинформированный узел запрашивает их; после получения перестаёт инициировать запросы, но продолжает отвечать другим. В push-pull запрос сам может нести информацию, а информированный получатель отвечает своими данными.
|
||||
|
||||
До запуска ответьте:
|
||||
|
||||
1. Какой подход быстрее распространит информацию в начале, когда источник один?
|
||||
2. Какой подход быстрее найдёт последних неинформированных участников?
|
||||
3. Должен ли самый быстрый вариант отправить меньше всего сообщений?
|
||||
|
||||
Запустите алгоритмы с одинаковыми параметрами:
|
||||
|
||||
```sh
|
||||
distsys-gossip -i push.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
|
||||
distsys-gossip -i pull.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
|
||||
distsys-gossip -i push_pull.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
|
||||
```
|
||||
|
||||
Заполните таблицу. Если полного охвата нет, укажите достигнутый охват и число отправлений к лимиту вместо `t_all` и `M_all`.
|
||||
|
||||
| Алгоритм | `n` | `f` | `d` | seed | `t_all` | `M_all` | Причина завершения |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| push | 1000 | 2 | 0 | 123 | | | |
|
||||
| pull | 1000 | 2 | 0 | 123 | | | |
|
||||
| push-pull | 1000 | 2 | 0 | 123 | | | |
|
||||
|
||||
Сравните начальный рост и доставку последним узлам. Объясните результат через действия отправителей. Флаг `-q` использует глобальную статистику симулятора: сами процессы не узнают, что сообщение уже получили все. В push и push-pull периодические обмены без этого ограничения продолжаются.
|
||||
|
||||
## 2. Изменение одного параметра
|
||||
|
||||
Выберите один алгоритм и сравните каждый следующий запуск с его базовым результатом. В командах замените `push.py` на выбранный файл.
|
||||
|
||||
Сначала увеличьте fanout с 2 до 4:
|
||||
|
||||
```sh
|
||||
distsys-gossip -i push.py -n 1000 -f 4 -s 123 -d 0 -t 60 -q
|
||||
```
|
||||
|
||||
Затем верните fanout 2 и добавьте вероятность потери 0,2:
|
||||
|
||||
```sh
|
||||
distsys-gossip -i push.py -n 1000 -f 2 -s 123 -d 0.2 -t 60 -q
|
||||
```
|
||||
|
||||
Для каждого запуска сначала запишите прогноз, затем добавьте строку результатов в таблицу. Как изменились время и цена охвата? Почему больше контактов за раунд не обязательно означает пропорционально меньшую задержку? Что можно заключить, если охват не достигнут за 60 единиц времени?
|
||||
|
||||
Один запуск показывает конкретное выполнение. При наличии времени повторите сравнение на seed `124` и `125`. Одинаковый seed воспроизводит запуск данной реализации; разные алгоритмы расходуют случайные числа по-разному, поэтому их контакты и потери не обязаны совпадать. Если меняете лимит времени, используйте один лимит для сопоставляемых запусков.
|
||||
|
||||
## 3. Полный охват и прекращение обменов
|
||||
|
||||
Изучите [push_pull_stop.py](push_pull_stop.py) и запустите его без `-q`:
|
||||
|
||||
```sh
|
||||
distsys-gossip -i push_pull_stop.py -n 1000 -f 2 -s 123 -d 0 -t 60
|
||||
```
|
||||
|
||||
При повторном получении информации через `got_info` узел с вероятностью 0,8 отменяет свой таймер и выдаёт `STOPPED`. Он продолжает отвечать на запросы. Неинформированные узлы продолжают искать данные; остановка таймера у держателя не удаляет доступную копию.
|
||||
|
||||
Выпишите показатели первой строки с полным охватом и показатели завершения. Совпадают ли они? Какой трафик возникает между ними? Подтвердите по `Finished`, что запуск завершился из-за отсутствия событий, а не по лимиту.
|
||||
|
||||
Для сравнения стоимости распространения используйте `M_all` всех четырёх вариантов. Число отправлений к концу запуска с остановкой — другая метрика. Итоговые `max/min/mean` отправлений по узлам также относятся к концу запуска, который может наступить позже полного охвата.
|
||||
|
||||
## Обсуждение
|
||||
|
||||
- Какие оптимизации могут уменьшить число повторных передач?
|
||||
- Почему успешный запуск при потерях не доказывает Uniform Agreement при отказах процессов?
|
||||
- Какая дополнительная информация и логика понадобятся для нескольких сообщений и причинного порядка доставки?
|
||||
|
||||
Сформулируйте вывод о скорости распространения, сетевых затратах и прекращении активности отдельно. Свяжите каждый вывод с наблюдением из таблицы и предположениями модели.
|
||||
@@ -0,0 +1,5 @@
|
||||
target
|
||||
**/target
|
||||
**/__pycache__
|
||||
**/.pytest_cache
|
||||
*.pyc
|
||||
@@ -0,0 +1,960 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anysystem"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01badd3bc0d042e4399f8f685c394a79a72e7f233b3d3b5eabb3b94734f4dbe"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"downcast-rs",
|
||||
"dyn-clone",
|
||||
"indexmap 2.14.0",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"ordered-float",
|
||||
"pyo3",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"regex",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simcore",
|
||||
"sugars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap 1.9.3",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distsys-gossip"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anysystem",
|
||||
"clap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sugars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
|
||||
dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.106",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_type_name"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92acc4cd6ae20767c54d6cf1a075624e7f4d9e99d7ebc685398ff243144d8714"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simcore"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20dfec85e94e569fa5b810b2d992c68b361f31bfba471447427536488c9c292"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"downcast-rs",
|
||||
"dyn-clone",
|
||||
"erased-serde",
|
||||
"futures",
|
||||
"log",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_type_name",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "sugars"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc0db74f9ee706e039d031a560bd7d110c7022f016051b3d33eeff9583e3e67a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "distsys-gossip"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
resolver = "3"
|
||||
|
||||
[dependencies]
|
||||
anysystem = "=0.3.0"
|
||||
clap = { version = "3.1.17", features = ["cargo", "derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sugars = "3"
|
||||
@@ -0,0 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM rust:1.97.1-bookworm@sha256:0e2bcaef56d041a486784e54104a81aebe0da44bd03019bd70bc0401e42e4a97 AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y build-essential python3-dev
|
||||
|
||||
WORKDIR /simulator
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=distsys-course-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
|
||||
--mount=type=cache,id=distsys-course-cargo-target-rust-1-97-1,target=/simulator/target,sharing=locked \
|
||||
cargo install --locked --path . --root /opt/course
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y python3-dev && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /opt/course/bin/distsys-gossip /usr/local/bin/distsys-gossip
|
||||
|
||||
WORKDIR /impl
|
||||
ENTRYPOINT ["distsys-gossip"]
|
||||
@@ -0,0 +1,204 @@
|
||||
use std::env;
|
||||
|
||||
use clap::{CommandFactory, ErrorKind, Parser};
|
||||
use serde::Serialize;
|
||||
use sugars::boxed;
|
||||
|
||||
use anysystem::python::PyProcessFactory;
|
||||
use anysystem::{Message, System};
|
||||
|
||||
// CLI -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Gossip simulator.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to Python file with process implementations.
|
||||
#[clap(long = "impl", short)]
|
||||
impl_path: String,
|
||||
|
||||
/// Number of nodes.
|
||||
#[clap(long, short, default_value = "10")]
|
||||
nodes: u32,
|
||||
|
||||
/// Network drop rate.
|
||||
#[clap(long, short, default_value = "0")]
|
||||
drop_rate: f64,
|
||||
|
||||
/// Fan-out (how many peers to contact on each round).
|
||||
#[clap(long, short, default_value = "1")]
|
||||
fanout: u32,
|
||||
|
||||
/// Stop simulation when all nodes delivered info.
|
||||
#[clap(long, short)]
|
||||
quick_mode: bool,
|
||||
|
||||
/// Time limit in simulation.
|
||||
#[clap(long, short, default_value = "60")]
|
||||
time_limit: u32,
|
||||
|
||||
/// Random seed.
|
||||
#[clap(long, short, default_value = "123")]
|
||||
seed: u64,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn validate(&self) -> Result<(), &'static str> {
|
||||
if self.nodes < 2 {
|
||||
return Err("--nodes must be at least 2");
|
||||
}
|
||||
if self.fanout == 0 || self.fanout >= self.nodes {
|
||||
return Err("--fanout must be between 1 and nodes - 1");
|
||||
}
|
||||
if !(0.0..=1.0).contains(&self.drop_rate) {
|
||||
return Err("--drop-rate must be a finite number between 0 and 1");
|
||||
}
|
||||
if self.time_limit == 0 {
|
||||
return Err("--time-limit must be positive");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
if let Err(message) = args.validate() {
|
||||
Args::command()
|
||||
.error(ErrorKind::ValueValidation, message)
|
||||
.exit();
|
||||
}
|
||||
env::set_var("PYTHONHASHSEED", args.seed.to_string());
|
||||
let proc_factory = PyProcessFactory::new(&args.impl_path, "Peer");
|
||||
println!("Nodes: {}", args.nodes);
|
||||
println!("Fanout: {}", args.fanout);
|
||||
println!("Network drop rate: {}", args.drop_rate);
|
||||
println!("Implementation: {}", args.impl_path);
|
||||
println!("Seed: {}", args.seed);
|
||||
println!("Time limit: {}", args.time_limit);
|
||||
println!("Quick mode: {}", args.quick_mode);
|
||||
|
||||
let mut sys = build_system(
|
||||
proc_factory,
|
||||
args.nodes,
|
||||
args.drop_rate,
|
||||
args.fanout,
|
||||
args.seed,
|
||||
);
|
||||
sys.send_local_message(
|
||||
"0",
|
||||
Message::json(
|
||||
"BROADCAST",
|
||||
&BroadcastMessage {
|
||||
info: "Some very important information to propagate to all nodes",
|
||||
},
|
||||
),
|
||||
);
|
||||
println!(
|
||||
"\n{:<10} {:<12} {:<12} {:<12}",
|
||||
"time", "delivered", "stopped", "messages"
|
||||
);
|
||||
let mut first_full_coverage = None;
|
||||
let (reason, delivered, stopped) = loop {
|
||||
let more_events = sys.step_for_duration(1.);
|
||||
let (delivered, stopped) = get_stats(&sys);
|
||||
println!(
|
||||
"{:<10} {:<12} {:<12} {:<12}",
|
||||
sys.time(),
|
||||
delivered,
|
||||
stopped,
|
||||
sys.network().network_message_count()
|
||||
);
|
||||
// Capture the first printed full-coverage sample, before any later traffic.
|
||||
if delivered == args.nodes && first_full_coverage.is_none() {
|
||||
first_full_coverage = Some((sys.time(), sys.network().network_message_count()));
|
||||
}
|
||||
if !more_events {
|
||||
break ("no pending events", delivered, stopped);
|
||||
}
|
||||
if args.quick_mode && delivered == args.nodes {
|
||||
break ("full coverage (--quick-mode)", delivered, stopped);
|
||||
}
|
||||
if sys.time() >= args.time_limit as f64 {
|
||||
break ("time limit reached", delivered, stopped);
|
||||
}
|
||||
};
|
||||
println!("\nFinished: {}", reason);
|
||||
println!(
|
||||
"Final state: time={}, delivered={}/{}, stopped={}, messages={}",
|
||||
sys.time(),
|
||||
delivered,
|
||||
args.nodes,
|
||||
stopped,
|
||||
sys.network().network_message_count()
|
||||
);
|
||||
match first_full_coverage {
|
||||
Some((time, messages)) => println!(
|
||||
"First full-coverage sample: time={}, messages={} (sampled every 1 time unit)",
|
||||
time, messages
|
||||
),
|
||||
None => println!("Full coverage: not reached"),
|
||||
}
|
||||
let sent_counts: Vec<u64> = sys
|
||||
.process_names()
|
||||
.iter()
|
||||
.map(|p| sys.sent_message_count(p))
|
||||
.collect();
|
||||
println!(
|
||||
"\nMessages sent by each node: max={}, min={}, mean={:.2}",
|
||||
sent_counts.iter().max().unwrap(),
|
||||
sent_counts.iter().min().unwrap(),
|
||||
sent_counts.iter().sum::<u64>() as f64 / sent_counts.len() as f64
|
||||
)
|
||||
}
|
||||
|
||||
// UTILS ---------------------------------------------------------------------------------------------------------------
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StartMessage {}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BroadcastMessage<'a> {
|
||||
info: &'a str,
|
||||
}
|
||||
|
||||
fn build_system(
|
||||
proc_factory: PyProcessFactory,
|
||||
nodes: u32,
|
||||
drop_rate: f64,
|
||||
fanout: u32,
|
||||
seed: u64,
|
||||
) -> System {
|
||||
let mut sys = System::new(seed);
|
||||
sys.network().set_delay(0.1);
|
||||
sys.network().set_drop_rate(drop_rate);
|
||||
for proc_id in 0..nodes {
|
||||
// process and node on which it runs have the same name
|
||||
let name = proc_id.to_string();
|
||||
sys.add_node(&name);
|
||||
let proc = proc_factory.build((proc_id, nodes, fanout), seed);
|
||||
sys.add_process(&name, boxed!(proc), &name);
|
||||
sys.send_local_message(&name, Message::json("START", &StartMessage {}))
|
||||
}
|
||||
sys
|
||||
}
|
||||
|
||||
fn get_stats(sys: &System) -> (u32, u32) {
|
||||
let mut delivered_count = 0;
|
||||
let mut stopped_count = 0;
|
||||
for proc in sys.process_names() {
|
||||
let outbox = sys.local_outbox(&proc);
|
||||
let msg_count = outbox.len();
|
||||
if msg_count > 0 {
|
||||
assert_eq!(outbox.first().unwrap().tip, "DELIVER");
|
||||
delivered_count += 1;
|
||||
}
|
||||
if msg_count == 2 {
|
||||
assert_eq!(outbox.get(1).unwrap().tip, "STOPPED");
|
||||
stopped_count += 1;
|
||||
}
|
||||
assert!(msg_count <= 2);
|
||||
}
|
||||
(delivered_count, stopped_count)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
sender:
|
||||
build:
|
||||
context: sender
|
||||
depends_on:
|
||||
- receiver1
|
||||
- receiver2
|
||||
- receiver3
|
||||
ports:
|
||||
- 9999:9999/udp
|
||||
receiver1:
|
||||
build:
|
||||
context: receiver
|
||||
receiver2:
|
||||
build:
|
||||
context: receiver
|
||||
receiver3:
|
||||
build:
|
||||
context: receiver
|
||||
@@ -0,0 +1,70 @@
|
||||
# IP multicast
|
||||
|
||||
Один контейнер принимает UDP-датаграммы на порту 9999 и пересылает их на multicast-адрес `224.0.2.0:10000`. Три контейнера-получателя присоединяются к этой группе и печатают полученные сообщения. В примере нет подтверждений, повторной передачи и восстановления пропусков.
|
||||
|
||||
## Запуск
|
||||
|
||||
Из корня репозитория перейдите в каталог примера и запустите контейнеры:
|
||||
|
||||
```sh
|
||||
cd materials/04-group-comm/seminar/ip_multicast
|
||||
docker compose up --build -d
|
||||
docker compose logs -f sender receiver1 receiver2 receiver3
|
||||
```
|
||||
|
||||
Дождитесь сообщения `ready` от отправителя и каждого получателя. Последняя команда показывает журналы и занимает терминал. Следующие команды выполняйте во втором терминале из того же каталога.
|
||||
|
||||
Отправьте пробное сообщение через Python 3:
|
||||
|
||||
```sh
|
||||
python -c "import socket; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'probe',('127.0.0.1',9999)); s.close()"
|
||||
```
|
||||
|
||||
При необходимости замените `python` на имя Python 3 в своём окружении. Убедитесь, что `probe` появился в журналах всех трёх получателей. Отправитель также печатает каждую пересылку. Имена сервисов в журнале позволяют различить получателей.
|
||||
|
||||
Если с хоста сообщения не доходят (нет `forwarded`), отправляйте из контейнера: `docker compose exec sender python -c "..."`.
|
||||
|
||||
## Эксперимент: получатель пропустил сообщение
|
||||
|
||||
До запуска предположите, какие получатели увидят `m1` и `m2` и получит ли вернувшийся участник пропущенные данные.
|
||||
|
||||
1. Остановите третьего получателя:
|
||||
|
||||
```sh
|
||||
docker compose stop receiver3
|
||||
```
|
||||
|
||||
2. Отправьте `m1`:
|
||||
|
||||
```sh
|
||||
python -c "import socket; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'm1',('127.0.0.1',9999)); s.close()"
|
||||
```
|
||||
|
||||
3. Верните третьего получателя:
|
||||
|
||||
```sh
|
||||
docker compose start receiver3
|
||||
```
|
||||
|
||||
4. Дождитесь **нового** сообщения `ready` от `receiver3` и снова отправьте `probe`. Убедитесь, что третий получатель его видит.
|
||||
5. Отправьте `m2`, заменив в команде `b'm1'` на `b'm2'`.
|
||||
|
||||
При штатной работе стенда `m1` увидят первый и второй получатели, а `m2` — все три. Третий получатель не получит `m1` задним числом: программа не хранит историю и не запрашивает пропущенные сообщения. UDP допускает потери и без отключения получателя; успешная проба не является доказательством гарантированной доставки.
|
||||
|
||||
Обсудите:
|
||||
|
||||
- Какие действия выполняет `IP_ADD_MEMBERSHIP` и где задаётся адрес группы?
|
||||
- Где в программе можно было бы добавить подтверждения или восстановление пропусков?
|
||||
- Почему получение сетевого пакета и надёжная доставка сообщения приложению — разные события?
|
||||
|
||||
Пустая UDP-датаграмма тоже является сообщением: `b''` не означает закрытие соединения. Можно отправить её и затем обычное сообщение, чтобы проверить, что оба процесса продолжают работу.
|
||||
|
||||
Повторный запуск контейнера в этом эксперименте иллюстрирует пропуск данных. В домашнем задании используется модель crash-stop: отказавшие процессы не возвращаются.
|
||||
|
||||
## Завершение
|
||||
|
||||
Остановите просмотр журналов сочетанием Ctrl+C, затем удалите контейнеры стенда:
|
||||
|
||||
```sh
|
||||
docker compose down
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-u", "receiver.py"]
|
||||
@@ -0,0 +1,12 @@
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.bind(('', 10000))
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, struct.pack('4sL', socket.inet_aton('224.0.2.0'), socket.INADDR_ANY))
|
||||
print("ready: joined 224.0.2.0, listening on UDP :10000", flush=True)
|
||||
while True:
|
||||
data, addr = sock.recvfrom(1024)
|
||||
print(f'got message {data!r} from {addr}', flush=True)
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-u" , "sender.py"]
|
||||
@@ -0,0 +1,14 @@
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
in_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
in_socket.bind(('', 9999))
|
||||
out_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
out_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, struct.pack('b', 1))
|
||||
print("ready: listening on UDP :9999, forwarding to 224.0.2.0:10000", flush=True)
|
||||
while True:
|
||||
data, addr = in_socket.recvfrom(1024)
|
||||
out_socket.sendto(data, ('224.0.2.0', 10000))
|
||||
print(f'forwarded {data!r} from {addr} to 224.0.2.0:10000', flush=True)
|
||||
@@ -0,0 +1,66 @@
|
||||
# Семинар 4. Групповые взаимодействия
|
||||
|
||||
На семинаре закрепим материал лекции через демонстрацию IP multicast, разбор историй отказов, работу с причинным буфером и эксперименты с gossip. Будем различать получение сетевого сообщения и его доставку приложению, проверять гарантии рассылки и сравнивать стоимость распространения информации.
|
||||
|
||||
## Подготовка и участие
|
||||
|
||||
Для самостоятельных экспериментов заранее выполните [инструкции по подготовке gossip](gossip/readme.md#подготовка) и проверьте запуск `distsys-gossip -h`. Если окружение не готово, можно участвовать в разборе демонстрации и подготовленных журналов: предсказывать результат, находить нужные показатели и объяснять наблюдения.
|
||||
|
||||
## 1. IP multicast: доставка сетевых сообщений
|
||||
|
||||
Проследим путь сообщения от отправителя к нескольким получателям в [примере с контейнерами](ip_multicast/readme.md). Найдём в коде адрес группы, присоединение получателя и отправку датаграммы.
|
||||
|
||||
Затем отключим одного получателя, отправим сообщение и вернём его в группу. Перед запуском предположим, кто увидит каждое сообщение, а после сравним прогноз с журналом.
|
||||
|
||||
Обсудим, восстанавливаются ли пропущенные сообщения и какие механизмы понадобились бы для этого. Свяжем наблюдение с различием между сетевым получением и доставкой приложению.
|
||||
|
||||
## 2. Надёжная рассылка: обязательства при отказах
|
||||
|
||||
Разберём свойства Validity, No Duplication, No Creation, Agreement и Uniform Agreement на историях с остановкой процессов. В каждой истории сначала определим, какие процессы корректны, затем проверим обязательства по доставке.
|
||||
|
||||
Основные вопросы:
|
||||
|
||||
- Что меняется, если отправитель падает посреди рассылки?
|
||||
- Обязаны ли остальные корректные процессы доставить сообщение, если получатель уже доставил его, а затем упал?
|
||||
- Почему перед вызовом `deliver` может потребоваться дождаться, пока сообщение сохранится у других процессов?
|
||||
|
||||
Сравним BEB, RB и URB и обсудим роль корректного большинства.
|
||||
|
||||
## 3. Порядок доставки: зависимости и буфер
|
||||
|
||||
На примерах последовательных сообщений и ответа в чате различим FIFO и Causal Order. Построим зависимости между сообщениями, полученными в перемешанном порядке, и пошагово разберём буфер доставки.
|
||||
|
||||
Для каждого сообщения определим, выполнены ли его зависимости и как доставка изменяет состояние получателя. Проверим, почему после одной доставки нужно снова просмотреть буфер и когда допустимы разные последовательности доставок.
|
||||
|
||||
В завершение обсудим пример независимых операций, для которых причинного порядка недостаточно, чтобы реплики получили одинаковый результат, и роль Total Order.
|
||||
|
||||
## 4. Gossip: скорость, трафик и остановка
|
||||
|
||||
В [лабораторной работе](gossip/readme.md) сравним push, pull и push-pull с одинаковыми параметрами. Перед каждым экспериментом сформулируем прогноз, затем сопоставим его с результатом.
|
||||
|
||||
- Измерим время полного охвата и число сетевых сообщений.
|
||||
- Изменим fanout и вероятность потерь, сохраняя остальные параметры.
|
||||
- Сравним полный охват с прекращением периодических обменов.
|
||||
- Разберём, почему `-q` останавливает симулятор, а `STOPPED` у процесса не означает его отказ.
|
||||
|
||||
Объясним наблюдения через действия узлов и ограничения модели. Результат одного запуска используем как конкретный пример; для проверки общего вывода повторим эксперимент с другими seed.
|
||||
|
||||
## Дополнительные материалы
|
||||
|
||||
### IP multicast
|
||||
|
||||
- [Адреса multicast](https://en.wikipedia.org/wiki/Multicast_address), [IGMP](https://linkmeup.gitbook.io/sdsm/9.-multicast/1.-igmp) и [подробный разбор multicast](https://linkmeup.gitbook.io/sdsm/9.-multicast).
|
||||
- [Работа с multicast в Python](https://pymotw.com/3/socket/multicast.html).
|
||||
- Примеры применения: [zeroconf](https://en.wikipedia.org/wiki/Zero-configuration_networking), [multicast DNS](https://en.wikipedia.org/wiki/Multicast_DNS), [multicast NTP](https://www.oreilly.com/library/view/cisco-ios-cookbook/0596527225/ch14s11.html), [обнаружение узлов в Apache Ignite](https://ignite.apache.org/docs/latest/clustering/tcp-ip-discovery).
|
||||
|
||||
### Надёжность и порядок
|
||||
|
||||
- [Отношение happens-before](https://en.wikipedia.org/wiki/Happened-before).
|
||||
- [Конспект курса Distributed Systems](https://www.cl.cam.ac.uk/teaching/2021/ConcDisSys/dist-sys-notes.pdf), разделы 3 и 4.
|
||||
|
||||
### Gossip и его применения
|
||||
|
||||
- [Визуализация распространения информации](https://flopezluis.github.io/gossip-simulator/).
|
||||
- Обнаружение узлов и сведения о составе группы: [Consul](https://developer.hashicorp.com/consul/docs/architecture/gossip), [обсуждение Serf, memberlist и SWIM](https://www.hashicorp.com/resources/everybody-talks-gossip-serf-memberlist-raft-swim-hashicorp-consul), [Cassandra](https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/architecture/archGossipAbout.html).
|
||||
- [Gossip в Hyperledger Fabric](https://hyperledger-fabric.readthedocs.io/en/latest/gossip.html).
|
||||
- Распространение блоков и транзакций Bitcoin: [обзор P2P-сети](https://nakamoto.com/bitcoins-p2p-network/), [статья](https://arxiv.org/pdf/1703.08761.pdf), [видеоматериалы](https://www.dsn.kastel.kit.edu/bitcoin/videos.html).
|
||||
Reference in New Issue
Block a user