-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.tex
1730 lines (1324 loc) · 173 KB
/
main.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\documentclass[a4paper,12pt]{extarticle}
\usepackage{geometry}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[english,russian]{babel}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{fancyhdr}
\usepackage{setspace}
\usepackage{graphicx}
\usepackage{colortbl}
\usepackage{tikz}
\usepackage{pgf}
\usepackage{subcaption}
\usepackage{listings}
\usepackage{indentfirst}
\usepackage[colorlinks,citecolor=blue,linkcolor=blue,bookmarks=false,hypertexnames=true, urlcolor=blue]{hyperref}
\usepackage[noabbrev]{cleveref}
\usepackage{indentfirst}
\usepackage{mathtools}
\usepackage{booktabs}
\usepackage[flushleft]{threeparttable}
\usepackage{tablefootnote}
% \usepackage{refcheck}
\usepackage{chngcntr} % нумерация графиков и таблиц по секциям
\counterwithin{table}{section}
\counterwithin{figure}{section}
\DeclareMathOperator*{\argmin}{arg\,min}
\graphicspath{{graphics/}}%путь к рисункам
\makeatletter
\renewcommand{\@biblabel}[1]{#1.} % Заменяем библиографию с квадратных скобок на точку:
\makeatother
\geometry{left=2.5cm}% левое поле
\geometry{right=1.5cm}% правое поле
\geometry{top=1.5cm}% верхнее поле
\geometry{bottom=1.5cm}% нижнее поле
\renewcommand{\baselinestretch}{1.25} % междустрочный интервал
\RequirePackage{float}
\newcommand{\goal}[0]{\texttt{Goal}}
\newcommand{\prune}[0]{\texttt{Prune}}
\newcommand{\invariant}[0]{\texttt{Invariant}}
\newcommand{\collect}[0]{\texttt{Collect}}
\newcommand{\mc}[0]{MC}
\addto\captionsrussian{\def\refname{Список литературы}}
\renewcommand{\theenumi}{\arabic{enumi}.}% Меняем везде перечисления на цифра.цифра
\renewcommand{\labelenumi}{\arabic{enumi}.}% Меняем везде перечисления на цифра.цифра
\renewcommand{\theenumii}{\arabic{enumii}.}% Меняем везде перечисления на цифра.цифра
\renewcommand{\labelenumii}{\arabic{enumi}.\arabic{enumii}.}% Меняем везде перечисления на цифра.цифра
\renewcommand{\theenumiii}{\arabic{enumiii}.}% Меняем везде перечисления на цифра.цифра
\renewcommand{\labelenumiii}{\arabic{enumi}.\arabic{enumii}.\arabic{enumiii}.}% Меняем везде перечисления на цифра.цифра
\crefname{section}{глава}{глава }
\Crefname{section}{Глава}{Глава }
\crefname{figure}{рис.}{рис. }
\Crefname{figure}{Рис.}{Рис. }
\crefname{table}{табл.}{табл. }
\Crefname{table}{Табл.}{Табл. }
\begin{document}
\input{title.tex}% это титульный лист
\newpage
{
\hypersetup{linkcolor=black}
\tableofcontents
}
\newpage
\textbf{Аннотация}
При тестировании распределенных систем возникает необходимость проверить все возможные ситуации, в которых может оказаться система.
Данная работа ставит целью использовать model checking, один из формальных методов для тестирования распределенных систем, в рамках лабораторных и домашних заданий курса <<Распределенные системы>>, реализованных в симуляционном фреймворке DSLab.
Работа предлагает оптимизации для метода model checking, модуль DependencyResolver, а также несколько специальных режимов: Instant и Collect Mode.
С помощью предложенных методов было найдено более 10 новых ошибок в архивных решениях заданий.
\textbf{Ключевые слова:} Model checking, распределенные системы, автоматическое тестирование, дискретно-событийное моделирование.
\hfill
\textbf{Annotation}
During the testing of distributed systems, it is crucial to examine all possible states that the system can get into.
This research uses model checking as a formal method to test distributed systems within the context of the <<Distributed Systems>> course assignments.
The simulations are conducted using the DSLab framework.
We propose various optimizations for model checking, including the DependencyResolver module, as well as the introduction of two special workflows: Instant and Collect modes.
These methods have successfully discovered over 10 previously unknown bugs in the archive of assignment solutions.
\textbf{Keywords:} Model checking, distributed systems, automated software testing, discrete event simulation.
\newpage
\section{Введение}
Современным компьютерным системам требуется обрабатывать запросы большого количества пользователей и работать под высокой нагрузкой.
Появляется необходимость в распределенных системах, которые за счет кооперативной работы могут обойти ограничения в ресурсах, существующие для одного сервера.
В то же время распределенные системы сложнее поддерживать и разрабатывать, особенно когда они начинают состоять из сотен или тысяч узлов.
В распределенных системах основным источником ошибок являются аппаратные и сетевые сбои, за счет которых нарушается синхронизация и обмен сообщениями между узлами в системе.
Для того чтобы неожиданный сбой не нарушил работу сервиса, необходимо полное тестирование корректности распределенной системы.
Данная работа исследует методы формальной верификации для распределенных систем.
Основным методом формальной верификации распределенной системы в данной работе является метод model checking (сокращенно \mc).
Работа выполнена на основе фреймворка DSLab \cite{b12} и его модуля DSLab-MP, который применяется для симуляции распределенных систем.
DSLab является потомком фреймворка dslib \cite{b11}, который используется для тестирования заданий курса "Распределенные системы" в Высшей школе экономики.
\subsection{Актуальность и значимость работы}
Каждое задание в рамках курса <<Распределенные системы>> требует от студента реализовать логику для процессов, соответствующую требованиям задачи.
В задаче есть набор тестов, каждый из которых отправляет набор запросов и ожидает от системы корректные ответы на запросы.
Некоторые тесты сделаны так, чтобы в промежуточный момент узел вышел из строя или сеть потеряла сообщение.
Такое искусственное внедрение отказов помогает найти ошибки в коде студента.
Также существуют рандомизированные тесты, которые выполняются много раз с разным случайным параметром, чтобы более тщательно проверить систему.
Такие тесты в рамках заданий называются <<chaos monkey>> тестами.
Описанные выше тесты, к сожалению, не могут проверить, что решение студента корректно работает при любом возможном выполнении.
Для того чтобы формально проверить прохождение даже базового теста, нужно использовать формальные методы, такие как \mc.
Более тщательная проверка студенческих решений приведет к улучшению навыков разработки и отладки распределенных систем у студентов, выполнявших задание.
\subsection{Цели и задачи ВКР}
Целью ВКР было улучшение тестов к практическим заданиям в фреймворке DSLab на основе метода model checking.
Для достижения этой цели были поставлены следующие задачи:
\begin{enumerate}
\item Изучение метода \mc\ и его применения для тестирования распределенных систем
\item Доработка ядра \mc\ в DSLab
\item Перенос заданий курса <<Распределенные системы>> на базу фреймворка DSLab
\item Разработка тестов к домашним заданиям по курсу <<Распределенные системы>> с использованием \mc
\item Анализ производительности тестов на основе \mc
\item Тестирование архивных студенческих решений на тестах в режиме \mc\ и анализ найденных ошибок в решениях
\end{enumerate}
\subsection{Основные результаты работы}
Данная работа развивает применение \mc\ в образовательных проектах, приводит опыт использования \mc\ в новых типах заданий, а также предлагает дополнительные режимы для тестирования распределенных систем.
Благодаря тестам к практическим заданиям было обнаружено больше 10 различных типов ошибок, которые не были найдены оригинальными тестами.
\subsection{Структура работы}
Дальнейшая структура работы выглядит следующим образом:
\begin{itemize}
\item \Cref{CHAPTER2} вводит модель распределенной системы, которая используется в работе, приводит обзор методов формальной верификации распределенной системы, а также приводит опыт использования таких методов в образовательных проектах.
\item \Cref{CHAPTER3} описывает реализацию model checking в DSLab, его функционал, ядро и модули.
\item \Cref{CHAPTER4} описывает подготовленные тесты к домашним заданиям в режиме model checking, найденные с помощью \mc\ ошибки в студенческих решениях, а также содержит общие рекомендации по написанию тестов к заданиям в режиме \mc.
\item \Cref{CHAPTER5} подводит итоги работы и предлагает дальнейшие направления работы над \mc\ в DSLab.
\end{itemize}
\section{Обзор литературы}
\label{CHAPTER2}
Существует большое количество способов протестировать корректность программы: ручные и автоматические тесты в разных окружениях, с искусственным внедрением ошибок и случайными конфигурациями.
Но такие методы не могут гарантировать корректность распределенной системы, они могут только увеличить уверенность, что она работает так, как от нее ожидается.
Существуют более надежные методы проверки корректности системы, которые будут рассмотрены в данной главе.
Также в главе описана использующаяся модель распределенной системы и приведен обзор образовательных проектов, использующих \mc.
\subsection{Модель распределенной системы в DSLab-MP}
Распределенная система состоит из набора узлов, каждый из которых соответствует серверу с запущенным инстансом сервиса.
DSLab-MP дает возможность симуляции отказа узлов и потери сообщений в сети.
На каждом узле запущен набор процессов.
Процессы могут отправлять сообщения друг другу.
Если сообщение отправлено процессу на другом узле, сообщение пройдет через модель сеть, которая эмулирует реальное сетевое соединение.
Сеть может эмулировать отказы, задержку или дубликацию сообщений.
Также процессы могут использовать таймеры для отложенных действий и отправлять сообщения в локальный лог.
Реализация каждого процесса выполняется в actor-based модели \cite{b21}.
Конфигурация актора предполагает определение набора методов, каждый из которых будет вызываться в определенных случаях.
Актор процесса реализует несколько методов:
\begin{enumerate}
\item \texttt{on\_local\_message}, реагирующий на получение сообщения от клиента системы. Он отвечает за обработку запросов.
\item \texttt{on\_message}, реагирующий на получение сообщения по сети.
\item \texttt{on\_timer}, реагирующий на срабатывание таймера.
\end{enumerate}
DSLab использует дискретно-событийное моделирование, чтобы моделировать работу распределенной системы.
В симуляции существует несколько типов событий.
Два основных типа события~--- срабатывание таймера и доставка сообщения по сети.
Такие события хранятся в очереди с приоритетами и симуляция шаг за шагом продвигается по времени путем обработки события с минимальным значением времени.
В дальнейшей работе термин \textit{состояние распределенной системы} используется, чтобы описать множество, содержащее в себе состояние всех процессов, состояние сети, события в симуляции.
Термином \textit{траектория исполнения} будет обозначаться последовательность состояний, связанных между собой последовательным прибавлением событий.
Можно считать, что каждая траектория исполнения является возможной последовательностью событий в системе, приводящую систему в заданное состояние.
\subsection{Формальная спецификация распределенной системы}
Корректность распределенной систему можно показать, записав ее на формальном языке, позволяющем явно сконструировать доказательство.
Примером такого языка для формальной спецификации является $\text{TLA}^+$, который активно используется в Amazon \cite{b31, b4}.
\subsubsection{$\text{TLA}^+$}
$\text{TLA}^+$ предлагает набор предикатов, которым можно описать свойства системы, и набор правил, которым можно разложить спецификацию из одной формулы с сотней параметров в набор модулей и предикатов.
В экосистеме $\text{TLA}^+$ предлагается программное обеспечение, проверяющее корректность спецификации \cite{b33}.
Такой подход, к сожалению, не подойдет для проверки заданий в рамках курса <<Распределенные системы>> по нескольким причинам.
Спецификация и реализация в большинстве случаев пишутся на разных языках, а значит, между ними создается разрыв, который может влиять на корректность.
Кроме этого, написание спецификации на формальном языке является сложной задачей, которая выходит за рамки умений студентов третьего курса.
\subsubsection{Verdi}
Существует фреймворк Verdi \cite{b3}, решающий одну из проблем $\text{TLA}^+$~--- существование разницы между спецификацией и реализацией.
Verdi построен поверх языка Coq \cite{b35}, который позволяет строить одновременно формальное доказательство и исполняемый файл.
Поскольку язык Coq ориентирован на формальные доказательства, реализации распределенных систем на Verdi (например, алгоритма Raft \cite{b34}) достаточно специфичны и сильно отличаются от промышленных реализаций.
За счет этого разработка на Verdi отличается от разработки распределенных систем, которые можно было бы использовать в практических целях.
Таким образом, было решено отказаться от формальной спецификации в пользу методов, которые пользуются только исходным кодом приложения.
\subsection{Model checking}
Model checking является методом проверки всех возможных путей исполнения теста в рамках системы.
Он используется для тестирования многопоточных приложений и распределенных систем \cite{b23,b22}.
\mc\ не дает формального доказательства, что система корректна, потому что делает исчерпывающее тестирование только для одного конкретного теста.
Тем не менее исследования \cite{b9} показывают, что большинство ошибок в распределенных системах можно отловить в сравнительно небольших окружениях.
Таким образом, прохождение даже небольшого теста в режиме \mc\ является весомым аргументом в пользу того, что система корректна.
Ниже приводится обзор промышленных model checker-ов для распределенных систем и многопоточных приложений, а также методов и техник, которые используют данные системы.
\subsubsection{CBSChecker}
CBSChecker --- это система, реализующая \mc\ для программ на языке C++ \cite{b5}.
Данная система проверяет работоспособность многопоточных приложений в модели памяти C++.
Для этого авторы анализируют поведение атомиков в C++, вводят отношения событий, примерами которых являются sequenced-before и happenes-before.
В построенной модели памяти можно выделить набор ограничений, которые авторы используют для ускорения обхода графа состояний.
Благодаря CBSChecker удалось найти ряд ошибок в ряде примитивов для многопоточной работы.
Похожие рассуждения о зависимости событий используются в главе \ref{dependencies}.
Благодаря анализу отношения happenes-before для модели распределенной системы получилось добавить набор оптимизаций в DSLab, а также убрать определенные невалидные состояния из траекторий \mc.
\subsubsection{FlyMc}
Проект FlyMc \cite{b6} является одним из самых эффективных на текущий момент model checker-ов для распределенных систем.
FlyMc предлагает три оптимизации для техники \mc, которые в комбинации дают достаточно высокий прирост в производительности, чтобы находить десятки ошибок в таких популярных системах, как Zookeeper \cite{b7} и Spark \cite{b8}.
Первой техникой является обработка симметричных состояний.
Состояния считаются симметричными, если их история различна, но сами по себе состояния не отличаются от уже рассмотренных.
Такое может быть возможно, например, при перенумеровании процессов.
Также возможны ситуации, когда в силу особенностей системы состояния, которые были получены разными тракеториями, обладают одинаковыми свойствами.
Второй техникой является отслеживание независимых событий.
Такие события могут обрабатываться системой в любом порядке, за счет чего можно фиксировать перестановки таких событий и кратно уменьшать размер графа состояний.
Примером независимых событий в системе может быть доставка сообщений между двумя парами узлов.
Третьей техникой являются параллельные свапы событий в траектории исполнения.
Эта оптимизация помогает перебирать траектории в произвольном порядке и раньше находить ошибки в системе.
Данная работа пользуется аналогичными техниками сжатия эквивалентных состояний (\cref{visitedstates}), а также частных случаев симметричных состояний (\cref{BROADCAST}) и применения независимых событий (\cref{equalmessages}).
\subsubsection{MaceMC}
\mc\ применяется в основном для проверки safety-свойств системы, то есть для таких свойств, которые могут быть нарушены в конкретном состоянии системы \cite{b30}.
Проверка liveness-свойств является более сложной задачей, потому что \mc\ в первую очередь проверяет свойства конкретного состояния, а свойство liveness относится к траекториям исполнения.
Тем не менее существуют адаптации \mc\ для проверки liveness, такие, как MaceMC \cite{b19}.
MaceMC использует комбинацию ограниченного по глубине DFS и случайного обхода для проверки достижения корректного состояния в конечном счете.
\mc\ сначала обходит все состояния на ограниченной глубине.
Траектории, в которых liveness-свойство еще не стабилизировалось, выбираются в качестве интересных для дальнейшего изучения.
Среди них берется случайная выборка, и из них запускается случайный обход, который в каждом состоянии переходит по случайному событию.
Если все обходы закончились выполнением liveness-свойства, то считается, что это свойство выполнено.
Если обход нашел траекторию, у которой liveness-свойство не выполнилось, происходит изучение траектории.
С помощью траекторий с общим началом находится промежуток событий, в котором система, вероятно, нарушило свои инварианты и тем самым потеряло свое liveness-свойство.
В данной работе была проведена подготовительная работа для подготовки тестов в режиме Random Walk (\cref{MEMBERSHIP}).
Также заметим, что авторы выделяют в своей работе идею, что liveness-свойство часто можно преобразовать в набор safety-свойств.
Такие safety-свойства, к сожалению, сложно задать, имея только определение liveness-свойства.
Зато при нарушении liveness-свойства часто можно выделить нарушенное safety-свойство и в будущем проверять именно его.
Аналогичный вывод был сформулирован в данной работе после создания тестов в режиме \mc касательно ошибок в системе, которые этими тестами находятся (\cref{posteriori}).
\subsection{Model checking в образовательных проектах}
В курсах по распределенным системам возникает необходимость тестирования студенческих решений.
Выделим особенности тестирования практических заданий, а не промышленных систем:
\begin{enumerate}
\item Практические задания по распределенным системам в большинстве случаев предлагают заготовку решения и формат взаимодействия пользователя (или тестирующей программы) с системой.
Заготовка решения для возможности тщательного тестирования, реализуется в рамках некоторого фреймворка.
За счет этого при тестировании учебных заданий у фреймворка без дополнительных модификаций есть необходимый контроль над поведением распределенной системы, позволяющий тестировать работу системы при различных видах отказов.
\item У промышленной системы одна реализация (помимо, возможно, имитирующих ее заглушек).
Реализация одной и той же системы у каждого студента своя.
Это значит, что тестирование системы должно проводиться без опоры на знание исходного кода процессов или нижележащего алгоритма, рассматривая исключительно внешнее поведение системы.
\end{enumerate}
\subsubsection{DSLabs}
Существует образовательный фреймворк DSLabs \cite{b1} для симуляции распределенных систем.
DSLabs, в отличие от DSLab, уже использует \mc\ для тестирования лабораторных работ (\cref{dslabsmc}).
Помимо стандартного обхода графа исполнения, авторы используют технику punctuate search, чтобы запускать \mc\ из промежуточного состояния системы, из которого нахождение ошибок более вероятно.
% Также авторы утверждают, что в ситуациях, когда граф исполнений бесконечен, глубина исследования графа должна соответствовать глубине, на которой лежат финальные состояния.
% Ведь если система некорректно себя ведет при другом порядке исполнения действий, соответствующее некорректное состояние окажется на сравнимой глубине.
Можно видеть, что лабораторные 3 и 4 (являющиеся самими объемными заданиями) не протестированы в полном объеме.
Это связано с большим размером графа исполнения.
Для увеличения уверенности в корректности системы используется punctuated search и длительное выполнение с отсечением по времени.
Данная работа во многом опирается на опыт DSLabs, реализуя подавляющее большинство функционала \mc\ в DSLabs.
Также развиваются некоторые идеи и появляется дополнительный функционал.
Например, punctuated search, который предлагает авторы, заменяется режимом \collect (\cref{collect}), который предлагает более исчерпывающий обход графа.
А очередь таймеров, которую авторы используют для отслеживания доступных событий, заменяется на \texttt{DependencyResolver}, который предлагает большее количество оптимизаций и режимов (\ref{dependencies}).
\begin{table}[htbp]
\caption{Лабораторные в DSLabs}
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\textbf{Название} & \textbf{Статус model checking} & \textbf{Размер решения, LOC} \\
\hline
0-pingpong & полный & 10 \\
\hline
1-clientserver & полный & 75 \\
\hline
2-primarybackup & полный & 300 \\
\hline
3-paxos & неполный & 400 \\
\hline
4-shardedstore & неполный & 1050 \\
\hline
\end{tabular}
\label{dslabsmc}
\end{center}
\end{table}
\subsubsection{Columbia University}
В курсе Distributed Systems Fundamentals, проводящемся в Columbia University, одним из домашних заданий является создание тестов для алгоритма Paxos с использованием метода \mc\ \cite{b20}.
Данное задание предполагает изучение студентами \mc\ и адаптацию собственной реализации Paxos в формате актора.
Благодаря этому заданию студенты формализуют ранее написанный алгоритм Paxos и тестируют его с помощью \mc.
Нужно заметить что такое применение \mc\ завязано на конкретное задание и не является методом, который можно будет применить в тестировании других домашних заданий.
\subsubsection{dslib}
Данной работе предшествовали работы по внедрению \mc\ для тестирования заданий курса <<Распределенные системы>> в фреймворк dslib \cite{b10,b32}.
Обе предшествующие работы фокусируются на разработке модуля, отвечающего за тестирование в режиме \mc, в рамках фреймворка dslib.
Итогами этих работ можно считать основное ядро компоненты \mc, которое было позже перенесено в DSLab.
Предыдущий опыт использования \mc\ в тестах в первую очередь проверял корректность реализации \mc\ и возможностей техники.
Текущая работа продолжает работу предшественников, но фокусируется не на технике \mc\ как таковой, а на модификациях \mc\ для тестирования практических заданий и подготовке тестов к заданиям.
\subsection{Выводы}
Для тестирования распределенных систем и получения формального подтверждения корректности решения можно использовать различные методы.
Был выбран метод \mc, так как подготовка решения к тестированию в режиме \mc\ не потребует дополнительной разработки от студента.
Помимо этого, интерпретация результата \mc\ прозрачна для студента --- при выявлении ошибочного поведения \mc\ позволяет в явном виде показать последовательность событий, которая привела к ошибке.
Были изучены различные промышленные программы для формальной верификации программ с целью поиска оптимизаций, которые можно использовать при работе над тестами к практическим заданиям.
Существуют случаи успешного использования \mc\ в образовательных проектах, но соответствующие реализации \mc\ оказываются ограничены в возможностях, за счет чего полное тестирование системы невозможно и используется отсечение по времени.
Данная работа предлагает развитие текущих идей, использующихся в образовательных проектах, а также предлагает опыт тестирования архивных студенческих решений.
\section{Реализация model checking в DSLab-MP}
\label{CHAPTER3}
Режим \mc\ в DSLab получает на вход экземпляр распределенной системы в требуемом для теста начальном состоянии, а потом совершает запуск исчерпывающего поиска.
Ядро \mc\ было реализовано ранее в рамках dslib \cite{b10}, и параллельно с выполнением данной работы шел перенос ядра в DSLab.
При разработке тестов появилась потребность в дополнительных модулях и режимах, которые были добавлены в ядро \mc.
\subsection{McSystem}
Работа с распределенной системой в режиме \mc\ происходит через структуру \texttt{McSystem}.
Система содержит информацию об узлах и процессах, сети, а также информацию о доступных событиях, которые могут произойти в текущий момент времени.
Состоянием системы называется множество состояний процессов и доступных событий.
Система может выдать свое текущее состояние или переключиться на заданное состояние с помощью методов \texttt{get\_state} и \texttt{set\_state}.
Ядро \mc\ позволяет применить к системе любое событие из доступных.
Поскольку \mc\ подразумевает перебор всех возможных траекторий исполнения программы, то перед применением события состояние системы запоминается для будущего использования.
За счет такого подхода в графе исполнений можно делать как переходы <<вперед>> --- с помощью применения события, так и <<назад>> --- с помощью загрузки системы в сохраненное ранее состояние.
\subsection{Сериализация процессов}
Для того чтобы сохранить состояние процесса, нужно понять, какие данные в решении являются важной частью состояния, а какие промежуточной информацией.
Самый первый вариант для сохранения состояния --- это явная реализация методов \texttt{get\_state} и \texttt{set\_state} для процесса.
Но такой подход требует от студента дополнительной сериализации, и этого хочется избежать.
Поскольку базовым языком для написания решений является Python, то сериализация состояния должна в первую очередь работать для него.
Так как любой актор является потомком базового класса \texttt{Process}, было принято решение сделать реализации \texttt{set\_state} и \texttt{get\_state} по умолчанию.
Эти методы считают, что все поля процесса важны для сериализации.
Изучение решений показывает, что это так, потому что данные, не относящиеся к состоянию процесса, обычно помечаются либо как глобальные константы, либо как локальные переменные.
Для реализации \texttt{get\_state} и \texttt{set\_state} используется библиотека pickle \cite{b24}.
С ее помощью любое состояние процесса превращается в строку из латинских символов и цифр с возможностью обратного преобразования (десериализации).
Использование pickle накладывает некоторые ограничения на решения.
Так как pickle сериализует данные по значениям, то согласованность ссылок может оказаться нарушена при десериализации.
Также pickle не сериализует объекты, являющиеся функциями.
Тем не менее эти случаи достаточно редки в студенческих решениях, и их исправление не является сложной задачей для студента, поэтому подобные ограничения вполне допустимы.
\subsection{Стратегии}
\mc\ работает на основе стратегий, определяющих алгоритм обхода графа состояний системы.
Интерфейс стратегии, помимо вспомогательных методов, реализует основной метод \texttt{search\_step}, который получает текущее состояние системы, и пытается создать новые состояния системы путем применения всех возможных событий.
Ядро \mc\ отвечает за то, чтобы применить новое событие, и вызвать \texttt{search\_step} для всех новых состояний системы.
На данный момент реализованы стратегии на основе DFS и BFS, и идет исследование стратегии Random Walk.
При вызове \texttt{search\_step} стратегия DFS по очереди применит каждое из доступных в этом состоянии событий к системе.
Для каждого применения события ядро \mc\ обновит состояние систему и сделает рекурсивный вызов \texttt{search\_step} для нового состояния системы.
При возврате из рекурсии состояние системы вернется в сохраненное на прошлом шаге.
Таким образом, стратегия DFS задает порядок обхода графа, соответствующий обходу в глубину.
Стратегия BFS обрабатывает события в порядке очереди.
Обработка происходит в одном цикле при вызове метода \texttt{run}, который является основным методом стратегии.
Для каждого состояния в очереди применяется каждое из доступных событий.
При вызове \texttt{search\_step} стратегия BFS добавляет состояние системы в очередь обработки.
Таким образом, стратегия BFS задает порядок обхода графа, соответствующий обходу в ширину.
Также была проведена первичная подготовка стратегии Random Walk.
Стратегия Random Walk при вызове \texttt{search\_step} применяет к системе случайное событие из доступных и делает рекурсивный переход в следующее состояние.
В отличие от стратегий DFS и BFS данная стратегия предлагает вероятностный обход траекторий исполнения, которые могут быть слишком длинными для стратегий с полным обходом графа состояний.
Такой подход может протестировать $n$ случайных траекторий исполнения на корректность.
Это может быть полезно в chaos monkey тестах, которые проверяют решение многократным запуском решения в случайных конфигурациях системы.
Добавление Random Walk позволит тестировать несколько случайных траекторий исполнения для случайных конфигураций систем.
Дополнительно отметим, что комбинация стратегий DFS и Random Walk может использоваться для тестирования liveness-свойств системы, как это сделано в MACEMC \cite{b19}.
\subsection{Goal, Prune, Invariant}
Для того чтобы кастомизировать стратегии под различные тесты, было принято решение добавить в стратегию методы \goal, \prune, \invariant\ аналогично тому, как это сделано в DSLabs \cite{b1}.
Каждый из этих методов является предикатом, который принимает состояние системы. На основе значений предикатов ядро \mc\ определяет дальнейшую работу алгоритма с состоянием (\cref{mcdiagram}).
\begin{figure}[H]
\begin{center}
\includegraphics[width=0.5\textwidth]{img/mc_diagram.png}
\caption{Схема работы \mc\ и разметки состояний}
\label{mcdiagram}
\end{center}
\end{figure}
Предикат \goal\ отвечает за определение того, является ли состояние терминальным.
В большинстве случаев \goal\ предполагает, что на все запросы процессы вернут ответы, а в системе больше не останется необработанных событий.
\prune\ отвечает за отсечение различных ветвей исполнения для оптимизации времени работы.
Чаще всего \prune\ используется для отсечения слишком глубоких состояний и добавления в систему дополнительных гарантий.
Например, \prune\ может гарантировать, что даже в сети с отказами сообщение будет доставлено спустя 3 попытки.
Для этого нужно, чтобы \prune\ отсекал все состояния, в которых количество потерянных сообщений больше, чем 2.
\invariant\ проверяет корректность состояния, то есть safety-свойства системы.
Если \invariant\ находит некорректное состояние, \mc\ завершает тест и помечает его как непройденный.
Если \mc\ не нашел ошибок, прошел по всему графу, ни разу не встретил некорректных состояний с помощью \invariant, и все ветви исполнения закончились либо предикатом \prune, либо предикатом \goal, то в таком случае решение считается прошедшим тест (\cref{mcgraph}).
\subsection{Эквивалентные состояния}
\label{visitedstates}
При работе с \mc\ было замечено, что одни и те же состояния могут появляться несколько раз, вследствие различных траекторий.
Например, отправка процессом 1 сообщения процессу 2 и отправка процессом 1 сообщения процессу 3 могут являться независимыми событиями (термин эквивалентности взят из FlyMc \cite{b6}).
Применение таких запросов в различном порядке не изменит финальное состояние системы, но увеличит размер графа состояний.
\begin{figure}
\begin{center}
\includegraphics[width=0.3\textwidth]{img/mc_graph_example.png}
\caption{Пример графа выполнения \mc}
\label{mcgraph}
\end{center}
\end{figure}
Для того чтобы не дублировать обход эквивалентных состояний, в DSLab была добавлена сущность \texttt{VisitedStates}.
Эта сущность является хеш-таблицей для проверки того, происходил ли запуск \mc\ из данного состояния ранее.
Поскольку сами по себе состояния системы хранят сериализованное состояние процессов и являются достаточно объемными, то \texttt{VisitedStates} поддерживает два режима: \texttt{Full} и \texttt{Partial}.
Режим \texttt{Full} явно хранит все состояния в хеш-таблице, а \texttt{Partial} разрешает ложно-положительные отсечения, так как хранит только хеши состояний.
Эксперименты показывают, что такой подход заметно уменьшает размер графа состояний (\cref{tab2}).
\begin{table}[htbp]
\caption{Связь количества состояний в графе для задания ping-pong (\cref{ping_pong_test_1}) с кешированием эквивалентных состояний}
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
& \textbf{Без кеширования} & \textbf{С кешированием} \\
\hline
\textbf{\# goal} & 677 & 87 \\
\hline
\textbf{\# prune} & 1422 & 196 \\
\hline
\end{tabular}
\label{tab2}
\end{center}
\end{table}
\subsection{Зависимости между событиями}
\label{dependencies}
Новые события появляются в системе, потому что они являются реакцией процесса на какое-то другое событие.
Но эти события не всегда могут быть доступными сразу --- вполне возможно, что одно событие при корректной работе симуляции может произойти только после второго события.
Контроль доступности событий является важной частью \mc, потому что за счет него проверяется корректность состояния, в котором оказалась система.
За этот контроль отвечают созданные в рамках работы модули \texttt{DependencyResolver} и \texttt{PendingEvents}.
\texttt{PendingEvents} является публичной сущностью, предлагающей контейнер для событий в распределенной системе с возможностью получения списка доступных событий.
\texttt{DependencyResolver} является центральной внутренней компонентой \texttt{PendingEvents}, которая отслеживает доступность событий.
\subsubsection{Отслеживание причинно-следственных связей}
\label{dependenciesdefinition}
В распределенных системах принято связывать события отношением happened-before \cite{b27}.
\texttt{DependencyResolver} должен отслеживать события, которые гарантированно связаны отношением happened-before.
События, которые могут быть упорядочены между процессами --- это срабатывание таймера и доставка сообщения.
При отсутствии гарантий на сеть возможно переупорядочивание любых сообщений между собой.
При отсутствии гарантий на задержки при доставке сообщений возможно переупорядочивание таймеров и сообщений.
Поскольку таймеры находятся на каждом процессе независимо и часы между узлами могут быть не синхронизированы, то возможно переупорядочивание таймеров, находящихся на различных узлах.
Таким образом, \texttt{DependencyResolver} должен отслеживать happened-before для таймеров, находящихся на одном процессе.
Например, если процесс $p$ поставил таймер $t_1$ с длительностью 3, и до истечения $t_1$ поставил таймер $t_2$ длительностью 5, то $t_1$ обязан случиться раньше, чем $t_2$ (\cref{timersgood}).
При этом, если в аналогичной ситуации $t_2$ будет иметь длительность 1, то порядок между $t_1$ и $t_2$ зависит от задержки между временами создания таймеров (\cref{timersbad}).
Поскольку таймеры в асинхронной actor-based модели появляются как реакция на другие события, они могут быть переупорядочены в зависимости от того, какая задержка была между событиями, после которых были поставлены таймеры.
Соответственно, можно гарантировать happened-before $t_1 \to t_2$, только если $\text{created}(t_1) \le \text{created}(t_2)$ и $\text{delay}(t_1) < \text{delay}(t_2)$.
\texttt{DependencyResolver} отслеживает такие зависимости и блокирует таймер $t_2$, если существует $t_1 \to t_2$, и $t_1$ еще не сработал.
Поскольку при обходе графа состояний время является относительной величиной, то \texttt{DependencyResolver} не поддерживает время в явном виде: для сравнения таймеров достаточно хранить $\text{delay}(t_i)$.
Сравнивать $\text{created}(t_i)$ можно с помощью момента добавления таймера в \texttt{DependencyResolver}~--- если таймер $t_1$ был добавлен в систему раньше таймера $t_2$, то
$\text{created}(t_1) < \text{created}(t_2)$.
\begin{figure}
\begin{center}
\includegraphics[width=0.5\textwidth]{img/dependency_resolvable.jpg}
\caption{При заведенном таймере на 3 секунды таймер $t$ с задержкой 5 обязан сработать после момента 3}
\label{timersgood}
\end{center}
\begin{center}
\includegraphics[width=0.5\textwidth]{img/dependency_unresolvable.jpg}
\caption{При заведенном таймере на 3 секунды таймеры $t$ и $t'$ с задержкой 1 неотличимы, и возможны обе ситуации}
\label{timersbad}
\end{center}
\end{figure}
\subsubsection{Обработка группы таймеров}
Дополнительно для оптимизации графа состояний была добавлена сортировка при одновременной работе с набором таймеров.
В ситуациях, где процесс создает несколько таймеров одновременно, ядро \mc\ последовательно добавляет эти таймеры в \texttt{DependencyResolver}.
В главе \ref{dependenciesdefinition} определено, что таймеры $t_1$ и $t_2$ связаны happened-before, если $\text{created}(t_1) \le \text{created}(t_2)$ и $\text{delay}(t_1) < \text{delay}(t_2)$.
При обработке группы таймеров выполняется свойство $\text{created}(t_1) = \text{created}(t_2)$.
Соответственно, обработанные в одной группе таймеры $t_1$ и $t_2$ связаны отношением happened-before, если $\text{delay}(t_1) < \text{delay}(t_2)$.
Поскольку \texttt{DependencyResolver} сравнивает созданные таймеры по моменту их добавления в систему, набор таймеров стоит добавлять в порядке возрастания времени ожидания, чтобы \texttt{DependencyResolver} последовательно связал все таймеры.
\subsubsection{Директивы}
Некоторые события, которые появляются в рамках работы \mc, должны случаться моментально и не зависят друг от друга.
Такими событиями являются отмена таймера и потеря сообщения.
Для них введен специальный тип событий <<директива>>.
Директивам выдается приоритет над другими событиями, поэтому директивы выполняются раньше, чем доставка сообщений или срабатывание таймеров, если есть такой выбор.
Чтобы не перебирать все перестановки имеющихся директив, ядро \mc\ считает не больше одной директивы доступной для выполнения в каждый момент времени.
\subsubsection{Оптимизация отправки дублирующихся сообщений}
\label{equalmessages}
Дополнительно \texttt{DependencyResolver} отвечает за оптимизацию при работе с одинаковыми сообщениями.
В заданиях, где процесс совершает повторную доставку сообщения после определенного времени, два эквивалентных сообщения, оказавшихся в сети, системой воспринимаются как разные.
Но поскольку сообщения эквивалентные, при выборе доступных событий имеет смысл доставлять только первое из них, чтобы уменьшить количество переходов в графе состояний (\cref{tab3}).
\begin{table}[htbp]
\begin{center}
\caption{Связь количества состояний в графе для задания ping-pong (\cref{ping_pong_test_1}) с ограничением на отправку дублирующихся сообщений}
\begin{tabular}{|c|c|c|}
\hline
& \textbf{До ограничения} & \textbf{После ограничения} \\
\hline
\textbf{\# goal} & 222 & 87 \\
\hline
\textbf{\# prune} & 629 & 196 \\
\hline
\end{tabular}
\label{tab3}
\end{center}
\end{table}
\subsubsection{Instant Mode}
Было замечено, что комбинация таймеров и сообщений в \mc\ иногда создает траектории, которые потенциально возможны, но не всегда интересны для изучения.
Например, в некоторых задачах требуется определять, вышел ли узел из строя с помощью таймеров.
В таком случае решение студента явно завязано на задержки в сети, и раннее срабатывание таймера приведет систему в состояние, где узел будет помечен отказавшим.
Несмотря на то, что такие состояния теоретически возможны, их тестирование не представляет интереса~--- подобные состояния могут стабилизироваться бесконечно долго, а логика теста, в котором узлы соединены стабильной сетью, чаще всего предполагает, что доставка по умолчанию будет работать корректно.
Разработанный режим Instant Mode предполагает, что сообщения в сети доставляются быстрее, чем срабатывают таймеры.
Таким образом, таймеры в Instant Mode срабатывают, только если все сообщения в сети уже доставлены.
Такой режим оказался полезным в случаях, когда в первую очередь интересно тестирование различных порядков доставки сообщений.
Благодаря этому методу получилось создать исчерпывающий \mc\ для заданий, в которых возможны бесконечные траектории исполнения и рост графа состояний имеет взрывной характер (\cref{MEMBERSHIP}, \cref{KVREPLICATION}).
\subsection{Collect}
\label{collect}
Для того чтобы тестировать систему в специальных состояниях, нужно задать это состояние, как стартовое.
Это не всегда возможно сделать в явном виде, потому что может предполагаться определенное внутреннее состояние системы.
Например, в задании kv-replication (\cref{KVREPLICATION}) существует требование на реализацию sloppy quorum и hinted handoff \cite{b17}.
Для создания состояния, которое проверяет sloppy quorum, сначала требуется нарушение работы системы, а затем ее восстановление.
Подобные случаи удобнее всего тестировать поэтапно.
Первый этап исследует состояния, в котором работа системы нарушена.
После первого этапа происходит восстановления системы.
Второй этап проверяет, что система восстанавливается, то есть достигает ожидаемого корректного состояния.
Можно заметить, что второй этап не может быть запущен сам по себе, потому что внутреннее состояние процессов после отказа хранит информацию о произошедших отказах.
За счет первого этапа происходит заведение системы в требуемое состояние.
Подобный подход для составления тестов в DSLabs называется punctuated search \cite{b1}.
\mc\ в DSLab использует специальный режим \collect\ для punctuated search.
В этом режиме стратегия получает дополнительный предикат, который она использует для сохранения состояний.
Сохраненные состояния из первого графа состояний будут использованы в качестве стартовых во втором графе состояний на измененной системе (\cref{pic3}).
Отличие \collect\ от оригинального punctuated search заключается в том, что \collect\ возвращает множество состояний, и из каждого из них можно запустить \mc.
Это может быть полезно, чтобы протестировать не только работу в произвольном неустойчивом состоянии, но и учесть историю попадания в это состояние.
Например, такой режим успешно используется для тестирования работы при отказе узла в задании <<Надежная упорядоченная рассылка>> (\cref{BROADCAST}), и учитывает момент, в который отключился узел.
Кроме этого, \collect\ можно рассматривать как <<рекурсивный>>, или <<поэтапный>> \mc\ --- когда выполняется \collect\ первого \mc-а, запускается второй этап.
Когда выполняется \collect\ второго этапа, запускается третий, и так далее.
Это оказалось удобно при составлении тестов на kv-хранилища (\cref{KVSHARDING}) --- запросы можно абстрагировать в отдельные методы, и вызывать их друг за другом в режиме \collect.
\begin{figure}
\begin{center}
\includegraphics[width=0.9\textwidth]{img/mc_collect.png}
\caption{Схема работы \mc\ в режиме collect}
\label{pic3}
\end{center}
\end{figure}
\subsection{McSummary}
Для того чтобы интерпретировать результаты работы \mc, имеет смысл получать вердикт, который можно прозрачно интерпретировать.
Недостаточно знать, нарушает решение инварианты или нет --- нужно понимать, как выглядел граф обхода.
Например, помимо проверок свойств safety нужно знать, что не нарушена гарантия liveness.
В таких случаях требуется проверить, что хотя бы один раз сработал \goal.
Для интерпретируемости результатов все предикаты возвращают строковый вердикт, и с помощью \texttt{McSummary}, специальной хеш-таблицы для подсчета статистики, возвращается общий подсчет.
Этот подсчет полезен не только для проверки liveness, но и для оценки размера графа или проведения экспериментов.
\subsection{Выводы}
В рамках работы по переносу ядра \mc\ из dslib в DSLab были созданы дополнительные модули и оптимизации.
Получившееся ядро \mc\ позволяет создавать поэтапное тестирование системы, настраивая режим тестирования, актуальный для задания.
Помимо этого, за счет использования предикатов \goal, \prune\ и \invariant\ стратегии в \mc\ легко кастомизируются под конкретное задание.
Также обновленное ядро \mc\ внимательнее относится к перебору состояний системы.
Во-первых, за счет использования DependencyResolver запрещен перебор невалидных последовательностей событий, которые раньше были возможны.
Во-вторых, был проведен ряд оптимизаций при переборе эквивалентных состояний: отсечение эквивалентных ветвей перебора, оптимизация работы с таймерами.
Добавленный функционал \collect\ помогает готовить гибкие тесты в режиме \mc, создавая поэтапные пайплайны тестирования.
Также был создан режим Instant Mode для удобного тестирования заданий, в которых требуются специальные гарантии на доставку сообщений.
Суммарный объем кода, проделанный в рамках совместного переноса ядра \mc, составляет примерно 1100 строк кода.
Ознакомиться с работой по переносу ядра \mc\ можно по \href{https://github.com/osukhoroslov/dslab/pull/172}{ссылке}.
Вкладом данной работы является несколько компонент: \texttt{McSummary}, \texttt{DependencyResolver}, \texttt{VisitedStates}.
Суммарный объем этих компонент составляет примерно 400 строк программного кода.
Режимы \collect\ и Instant Mode добавлены в рамках улучшения ядра \mc, вместе с небольшими техническими изменениями и оптимизациями.
Общий объем изменений составил примерно 300 строк программного кода и ознакомиться с ним можно по \href{https://github.com/KiK0S/dslab/pull/12}{ссылке}.
\section{Применение model checking для тестирования решений заданий}
\label{CHAPTER4}
В курсе <<Распределенные системы>> есть 7 заданий на основе dslib --- одна лабораторная работа и 6 домашних заданий.
Каждое задание было перенесено из dslib \cite{b11} в DSLab.
Для перенесенных заданий были подготовлены тесты на \mc.
Тесты были провалидированы на архивном наборе студенческих решений.
\subsection{Ping-Pong}
В лабораторной работе <<ping-pong>> \cite{b28} требуется написать логику работы процесса-клиента и процесса-сервера.
Процесс-клиент, получая локальное сообщение, шлет сообщение серверу, и ожидает от него ответа.
Предполагается, что в случае ошибок при доставке сообщения, клиент, пользуясь таймером, повторно отправит сообщение серверу.
В тестах к данному заданию система состоит из 2 узлов~--- клиента и сервера.
\begin{table}[htbp]
\caption{Тесты для ping-pong}
\begin{center}
\begin{tabular}{|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.3\textwidth}|}
\hline
Тест & \goal & \invariant & \prune \\
\hline
mc reliable network & Получение 2 сообщений & Имеющиеся ответы верны & Каждый из узлов отправил не больше 4 сообщений \\
\hline
mc unreliable network & Получение 2 сообщений & Имеющиеся ответы верны & Глубина состояния не больше 7 \\
\hline
mc limited drops & Получение 2 сообщений & Имеющиеся ответы верны & Не больше 3 потерь сообщения, узел отправил не больше 5 сообщений \\
\hline
\end{tabular}
\label{testspingpong}
\end{center}
\end{table}
\subsubsection{Тест mc\_reliable\_network}
\label{ping_pong_test_1}
Тест симулирует клиента, который отправляет два запроса.
В данном тесте сеть не теряет сообщения, но не дает гарантий на время доставки.
В данном задании технически возможны бесконечные траектории.
Например, возможна ситуация, когда таймер для повторной отправки сообщения всегда срабатывает раньше, чем доставка сообщения.
В таком случае система будет создавать все новые сообщения для доставки по сети, но при этом сообщение так и не будет доставлено.
Для оптимизации размера графа состояний используется \prune, который отсекает состояния, в которых какой-либо из узлов отправил больше 4 сообщений.
Такого отсечения достаточно, чтобы протестировать разные траектории исполнения, необязательно соответствующие идеальным условиям.
При этом такой подход исключает бесконечные цепочки в корректных решениях --- отсечение цепочек из сообщений означает, что возможны только бесконечные цепочки из таймеров, но любое корректное решение не запускает цепочку таймеров, если в результате она не отправит сообщение.
Соответственно, \mc\ может быть выполнен за конечное время вне зависимости от вида решения.
\subsubsection{Тест mc\_unreliable\_network}
\label{ping_pong_test_2}
Тест симулирует клиента, который отправляет два запроса.
В данном тесте сеть может терять сообщения.
В данном задании технически возможны бесконечные траектории.
Так как сеть не гарантирует конечную доставку сообщения, существуют цепочки, где сообщение так и не было доставлено.
Поэтому \mc\ использует отсечение слишком глубоких состояний с помощью \prune.
В качестве глубины для отсечения выбрана глубина 7, так как она больше минимального количества шагов для прохождения теста равно 4 (по 2 сообщения на каждый запрос).
Для глубины 7 обход делается за несколько секунд, и при этом тестируются траектории, в которых помимо базовой доставки сообщений случится потеря сообщения.
\subsubsection{Тест mc\_limited\_drops}
\label{ping_pong_test_3}
Данный тест решает проблему прошлого теста (\cref{ping_pong_test_2}).
Бесконечные цепочки возникают, когда происходит бесконечная потеря сообщений.
Для того чтобы сделать исчерпывающий \mc, достаточно поставить лимит на количество потерянных сообщений, и свести тест к уже имеющейся модели (\cref{ping_pong_test_1}).
Это значит, что дополнительно понадобится отсечение на количество отправленных сообщений, которое автоматически повлечет за собой отсечение по количеству таймеров.
Для оптимизации размера графа состояний используется \prune, который отсекает состояния с более чем 3 потерями сообщений.
\subsubsection{Результаты}
Для данного задания отсутствует архив студенческих решений, так что тестирование проводилось на 2 авторских решениях (одном корректном и одном некорректном).
Результаты тестирования приведены в \cref{tab4}.
Тесты \mc\ (\cref{testspingpong}) нашли гарантию, на которую неявно полагалось авторское решение, но которой не было дано в условии.
Авторское решение предполагало, что новый запрос от клиента может прийти только тогда, когда система ответила на предыдущий запрос.
\mc\ привел систему в состояние, когда два запроса в ней существовали одновременно, и в таком случае в некоторых траекториях исполнения решение получало ошибку выполнения.
Таким образом, было добавлено третье решение, исправляющее ошибку в авторском решении.
\begin{table}[htbp]
\caption{Тесты model checking в задании Ping-Pong}
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\textbf{Тест} & \textbf{\# accepted} & \textbf{\# failed} \\
\hline
mc\_reliable\_network & 2 & 1 \\
\hline
mc\_unreliable\_network & 1 & 2 \\
\hline
mc\_limited\_drops & 1 & 2 \\
\hline
\end{tabular}
\label{tab4}
\end{center}
\end{table}
\subsection{Гарантии доставки}
\label{GUARANTEES}
В задании <<Гарантии доставки>> \cite{b13} требуется реализовать доставку сообщений с соблюдением различных гарантий:
\begin{enumerate}
\item At Least Once: каждое отправленное сообщение будет получено хотя бы один раз.
\item At Most Once: каждое отправленное сообщение будет получено не более одного раза.
\item Exactly Once: каждое отправленное сообщение будет получено ровно один раз.
\item Exactly Once Ordered: то же самое, что и Exactly Once, но сообщения должны быть доставлены в соответствии с порядком отправки.
\end{enumerate}
В тестах к данному заданию система состоит из 2 узлов~--- отправителя и получателя.
Доставку требуется реализовывать между процессами \texttt{sender} и \texttt{receiver}, расположенных на соответствующих узлах.
Предполагается, что решение реализует каждую из гарантий по отдельности, с помощью комбинации механизмов повторной доставки (необходимый для At Least Once), отсеивания дубликатов (необходимый для At Most Once), и упорядочивания сообщений с помощью временных меток (для Ordered-гарантии).
\begin{table}[htbp]
\caption{Тесты для задания <<Гарантии доставки>>}
\begin{center}
\begin{tabular}{|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.3\textwidth}|}
\hline
Запуск \mc & \goal & \invariant & \prune \\
\hline
mc reliable network & Доставка 2 сообщений & Имеющиеся ответы верны & Узел отправляет не более 4 сообщений и ставит не более 4 таймеров \\
\hline
mc unreliable network & Доставка 2 сообщений & Имеющиеся ответы верны & Глубина состояния не больше 5 \\
\hline
mc limited drops & Доставка 2 сообщений & Имеющиеся ответы верны & Не больше 3 потерь сообщений, узел отправляет не более 4 сообщений и ставит не более 4 таймеров \\
\hline
\end{tabular}
\label{testsguarantees}
\end{center}
\end{table}
\subsubsection{Тест mc\_reliable\_network}
\label{guarantees_test_1}
Тест симулирует клиента, который отправляет два запроса.
В данном тесте сеть не теряет сообщения, но не дает гарантий на время доставки.
В данном задании технически возможны бесконечные траектории.
Например, возможна ситуация, когда таймер для повторной отправки сообщения срабатывает раньше, чем доставка сообщения.
Для оптимизации размера графа состояний используется \prune, который выполняет отсечение по количеству отправленных с каждого узла сообщений и заведенных таймеров.
Такой подход исключает бесконечные цепочки в корректных решениях.
\subsubsection{Тест mc\_unreliable\_network}
\label{guarantees_test_2}
Тест симулирует клиента, который отправляет два запроса.
В отличие от прошлого теста (\cref{guarantees_test_1}), данном тесте сеть может терять сообщения.
В данном задании технически возможны бесконечные траектории.
Так как сеть не гарантирует конечную доставку сообщения, существуют цепочки, где сообщение так и не было доставлено.
Поэтому \mc\ использует отсечение по глубине обхода.
Выбрана глубина обхода 5 для баланса между временем выполнения теста и найденными ошибками.
Можно заметить, что для доставки двух сообщения достаточно 2 сообщений в сети.
Запас в 3 дополнительных события означает, что также будут проверены safety-свойства в состояниях, где успели дополнительно сработать таймеры, произойти дополнительные доставки сообщений, потери сообщений или доставка сообщений-подтверждений от получателя.
\subsubsection{Тест mc\_limited\_drops}
\label{guarantees_test_3}
Данный тест решает проблему прошлого теста (\cref{guarantees_test_2}).
Бесконечные цепочки возникают, когда происходит бесконечная потеря сообщений.
Для того чтобы сделать исчерпывающий \mc, достаточно поставить лимит на количество потерянных сообщений, и свести тест к уже имеющейся модели (\cref{guarantees_test_1}).
Для оптимизации размера графа состояний используется \prune, который отсекает состояния с большим количеством потерянных сообщений, отправленных сообщений и заведенных таймеров.
Также \prune\ отсекает состояния с более чем 3 потерями сообщений.
Такой тест оказывается полным, и тестирует большое количество различных траекторий и случаев при выполнении теста.
\subsubsection{Результаты}
Результаты тестирования приведены в \cref{tab5}.
При тестировании (\cref{testsguarantees}) архивных студенческих решений были найдены следующие ошибки:
\begin{enumerate}
\item Решение делало отложенную очистку кеша и забывало факт доставки сообщения, за счет чего доставка одного и того же сообщения могла случиться несколько раз.
Такая ошибка встретилась в двух решениях.
\item Решение на гарантию At Most Once при срабатывании таймера могло проигнорировать сообщение $X$ как полученное ранее, даже если $X$ доставлено впервые.
Несмотря на то, что формально это At Most Once, в задаче явно оговорено, что при отсутствии отказов сети решение должно доставлять запросы.
\end{enumerate}
Можно заметить, что в этом задании \mc\ в основном находит ошибки в решениях, которые полагались на таймеры, и преждевременное срабатывание таймера могло нарушить инварианты.
Такие решения слишком сильно опирались на свойства сети (а именно задержку при доставке), за счет чего могут вести себя некорректно, если задержка изменится.
\begin{table}[htbp]
\caption{Результаты тестирования студенческих решений в задании <<Гарантии доставки>>. Рядом с числом ошибочных решений в скобках указано, сколько из них ранее были оценены на максимально возможный балл. }
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\textbf{Тест [гарантия]} & \textbf{\# accepted} & \textbf{\# failed} \\
\hline
mc\_reliable\_network [AMO] & 51 & 39 (1) \\
mc\_reliable\_network [ALO] & 52 & 38 (0) \\
mc\_reliable\_network [EO] & 49 & 41 (2) \\
mc\_reliable\_network [EOO] & 48 & 42 (0) \\
\hline
mc\_unreliable\_network [AMO] & 51 & 39 (1) \\
mc\_unreliable\_network [ALO] & 52 & 38 (0) \\
mc\_unreliable\_network [EO] & 49 & 41 (2) \\
mc\_unreliable\_network [EOO] & 48 & 42 (0) \\
\hline
mc\_limited\_drops [AMO] & 51 & 39 (1) \\
mc\_limited\_drops [ALO] & 51 & 39 (0) \\
mc\_limited\_drops [EO] & 49 & 41 (2)\\
mc\_limited\_drops [EOO] & 46 & 44 (0)\\
\hline
\end{tabular}
\label{tab5}
\end{center}
\end{table}
\subsection{Надежная упорядоченная рассылка}
\label{BROADCAST}
В задании <<Надежная упорядоченная рассылка>> \cite{b14} требуется доставить сообщение большинству узлов в кластере.
Узлы могут отказывать, но гарантируется, что после отказа они не восстанавливаются до конца теста.
Доставка сообщений надежная.
Таким образом, единственные сбои могут возникнуть из-за того, что узел вышел из строя.
Узлы, которые не отказали до конца теста, называются корректными.
Гарантируется, что более половины узлов в системе корректны.
К решению предлагаются следующие обязательные требования:
\begin{enumerate}
\item No Creation: каждое принятое сообщение должно быть предварительно отправлено
\item No Duplication: каждое принятое сообщение должно быть доставлено единожды
\item Validity: если корректный узел $n$ отправил другому узлу сообщение $m$, то $n$ обязательно доставит $m$
\item Uniform agreement: если узел $n$ (необязательно корректный) принял сообщение $m$, то $m$ в конечном счете будет принято на каждом корректном узле.
\end{enumerate}
Предполагается, что процессы рассылают сообщения всем остальным процессам в сети, и собирают информацию о том, набралось ли большинство процессов, готовых доставить сообщение.
В тестах к данному заданию система состоит из 3 узлов.
Таким образом, гарантируется корректность хотя бы 2 узлов.
\begin{table}[htbp]
\caption{Тест mc\_normal}
\begin{center}
\begin{tabular}{|p{0.15\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth} |}
\hline
Этап & \goal & \invariant & \prune & \collect \\
\hline
Доставка & Сообщение доставлено всеми корректными узлами & Соблюден формат доставленных клиенту сообщений & Каждый узел отправил не больше 2 сообщений, эквивалентные состояния игнорируются, глубина не более 10 & - \\
\hline
\end{tabular}
\label{testsbroadcast_1}
\end{center}
\end{table}
\subsubsection{Тест mc\_normal}
Тест соответствует нормальной рассылке сообщения, когда отказов не существует.
Теоретически, в данном задании не должно возникать бесконечных цепочек.
На практике оказалось, что обход может занимать несколько минут даже для 3 узлов.
Это связано с тем, что типичное решение рассылает сообщения по логике <<от всех всем>>.
Таким образом, возможны перестановки сообщений, которые увеличивают размер графа состояний экспоненциально.
В данном задании важно сделать правильные отсечения с помощью \prune.
В тесте, помимо ограничений на состояние, делается проверка состояний на <<эквивалентность>>.
Предполагается, что узлы одинаковы между собой, поэтому любая перестановка узлов не меняет состояние.
По этой причине отсечение проверяет, что порядок, в котором узлы "встречаются" в последовательности, соответствуют тривиальной перестановке узлов.
\subsubsection{Тест mc\_sender\_crash}
\begin{table}[htbp]
\caption{Тест mc\_sender\_crash}
\begin{center}
\begin{tabular}{|p{0.15\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth}|p{0.2\textwidth} |}
\hline
Этап & \goal & \invariant & \prune & \collect \\
\hline
До отказа & Сообщение доставлено всеми корректными узлами & Соблюден формат доставленных клиенту сообщений & Глубина не более 4 & Хотя бы один узел получил сообщение \\
\hline
Выведение из строя отправителя & - & - & - & -\\
\hline
Доставка после отказа & Сообщение доставлено всеми корректными узлами & Соблюден формат доставленных клиенту сообщений & Каждый узел отправил не больше 4 сообщений, эквивалентные состояния игнорируются, глубина не более 6 & - \\
\hline
\end{tabular}
\label{testsbroadcast_2}
\end{center}
\end{table}
Тест предполагает, что по ходу выполнения программы узел-отправитель сообщения откажет.
Событие отказа узла является достаточно сложным для моделирования, потому что, например, узел отправителя сообщения не может отказать, пока он не отправил сообщение хотя бы одному корректному узлу.
Для того чтобы правильно отслеживать такие события, используется механизм punctuated search.
С помощью режима \collect\ делается первый запуск \mc, который собирает все состояния, в которых отправитель отправил сообщение хотя бы одному другому узлу.
После этого для каждого собранного состояния выводится из строя процесс-отправитель, и запускается новый \mc, который проверяет, что даже с учетом отказавшего отправителя сообщение доставится на большинство узлов.
Таким образом, в рамках этого теста происходит много разных запусков \mc, которые делают полный обход графа состояний вместе с punctuated search, что делает более точное тестирование, чем оригинальный punctuated search в DSLabs \cite{b1}.
\subsubsection{Результаты}
Результаты тестирования приведены в \cref{tab6}.
При тестировании (\cref{testsbroadcast_1} и \ref{testsbroadcast_2}) архивных студенческих решений были найдены следующие ошибки:
\begin{enumerate}
\item Решение делало некорректную инициализацию ресурсов, которая опиралось на то, что сообщение от процесса самому себе будет доставлено раньше, чем случится коммуникация между двумя различными процессами.
\item Решение некорректно проверяло необходимость локальной доставки сообщения. Счетчик инициализировался таким образом, что в системе из трех узлов решение пропускало момент, когда счетчик набирал кворум, за счет чего сообщение не доставлялось.
\item 7 решений неэффективно использовали сеть --- отдельно производили массовую рассылку целевого сообщения и отдельно массово рассылали факт получения данного сообщения, хотя данные сообщения можно объединить и снизить нагрузку на сеть в 2 раза.
\end{enumerate}
\begin{table}[htbp]
\caption{Результаты тестирования студенческих решений в задании <<Надежная упорядоченная рассылка>>. Рядом с числом ошибочных решений в скобках указано, сколько из них ранее были оценены на максимально возможный балл. }
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
\textbf{Тест} & \textbf{\# accepted} & \textbf{\# failed} \\
\hline
mc\_normal & 69 & 11 (10) \\
\hline
mc\_sender\_crash & 69 & 11 (10) \\