Add week 4 materials

This commit is contained in:
2026-10-01 22:08:30 +03:00
parent 6210b46c1f
commit 6008cff9f6
21 changed files with 1707 additions and 0 deletions
Binary file not shown.
+10
View File
@@ -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)
+66
View File
@@ -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).