diff --git a/.gitignore b/.gitignore
index d4bfe7a..742425f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
tuple-space/.vs/
tuple-space/Client/obj/
+tuple-space/**/obj
+tuple-space/**/bin/
+
+*.cache
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index fb32d71..3400513 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,10 @@
-# DAD
\ No newline at end of file
+# DAD
+Design and Implementation of Distributed Applications - Tuple Space
+## Requirements
+The project was implemented using C# and .Net remoting.
+- Microsoft Visual Studio (alternatively MonoDevelop)
+- Common Languange Runtime (alternatively Mono Runtime)
+- .Net Framework 4.6.1+
+
+## How to run
+Compile the code in the Microsft Visual Studio and run the ProcessCreationTime and then the PuppetMaster.
diff --git a/docs/dida_report.pdf b/docs/dida_report.pdf
new file mode 100644
index 0000000..ce06241
Binary files /dev/null and b/docs/dida_report.pdf differ
diff --git a/docs/dida_report_latex/Latex8.ps b/docs/dida_report_latex/Latex8.ps
new file mode 100644
index 0000000..ff84e79
Binary files /dev/null and b/docs/dida_report_latex/Latex8.ps differ
diff --git a/docs/dida_report_latex/Latex8.txt b/docs/dida_report_latex/Latex8.txt
new file mode 100644
index 0000000..29345a1
--- /dev/null
+++ b/docs/dida_report_latex/Latex8.txt
@@ -0,0 +1,21 @@
+========== file: LATEX.TXT =======================
+
+The following files contain the LaTeX macros necessary for you to format
+your paper based on the instructions/measurements in the INSTRUCT.TXT
+attachment in our standard author kit for 8.5 x 11-inch proceedings.
+The files appear in the following order:
+
+latex8.sty -- the style file
+latex8.tex -- the main macro set
+latex8.bib -- the sample bibliography macros
+latex8.bst -- the bibliography main macro set
+latex8ps.txt -- this file, when printed out, will give you a sample
+of how your paper should look when it's finished. This file is in a
+separate attachment from the first four LaTeX files. Remember to save
+it as a .ps file and strip out any extraneous lines at the top of the
+file to enable successful printing.
+
+Note that both current versions of LaTeX, 2e and 2.09, have the same
+macros -- refer to the start of the latex8.sty file for the opening
+statement for each.
+
diff --git a/docs/dida_report_latex/images/dad_architecture.png b/docs/dida_report_latex/images/dad_architecture.png
new file mode 100644
index 0000000..489cafb
Binary files /dev/null and b/docs/dida_report_latex/images/dad_architecture.png differ
diff --git a/docs/dida_report_latex/images/dad_smr_normal.png b/docs/dida_report_latex/images/dad_smr_normal.png
new file mode 100644
index 0000000..2b7ad40
Binary files /dev/null and b/docs/dida_report_latex/images/dad_smr_normal.png differ
diff --git a/docs/dida_report_latex/images/ifd.png b/docs/dida_report_latex/images/ifd.png
new file mode 100644
index 0000000..cb803e9
Binary files /dev/null and b/docs/dida_report_latex/images/ifd.png differ
diff --git a/docs/dida_report_latex/images/manyreads.png b/docs/dida_report_latex/images/manyreads.png
new file mode 100644
index 0000000..b8be5b2
Binary files /dev/null and b/docs/dida_report_latex/images/manyreads.png differ
diff --git a/docs/dida_report_latex/images/manytakes.png b/docs/dida_report_latex/images/manytakes.png
new file mode 100644
index 0000000..bdfa58b
Binary files /dev/null and b/docs/dida_report_latex/images/manytakes.png differ
diff --git a/docs/dida_report_latex/images/pfd.png b/docs/dida_report_latex/images/pfd.png
new file mode 100644
index 0000000..ce1e62c
Binary files /dev/null and b/docs/dida_report_latex/images/pfd.png differ
diff --git a/docs/dida_report_latex/latex8.bib b/docs/dida_report_latex/latex8.bib
new file mode 100644
index 0000000..50f2f0d
--- /dev/null
+++ b/docs/dida_report_latex/latex8.bib
@@ -0,0 +1,86 @@
+
+%
+% $Description: Sample bibliography$
+%
+% $Author: ienne $
+% $Date: 1995/09/15 15:19:53 $
+% $Revision: 1.3 $
+%
+
+@inproceedings{Xu1989ADF,
+ title={A design for a fault-tolerant, distributed implementation of Linda},
+ author={Andrew Xu and Barbara Liskov},
+ booktitle={FTCS},
+ year={1989}
+}
+
+
+@article{Bakken:1995:SFP:203121.203132,
+ author = {Bakken, David Edward and Schlichting, Richard D.},
+ title = {Supporting Fault-Tolerant Parallel Programming in Linda},
+ journal = {IEEE Trans. Parallel Distrib. Syst.},
+ issue_date = {March 1995},
+ volume = {6},
+ number = {3},
+ month = mar,
+ year = {1995},
+ issn = {1045-9219},
+ pages = {287--302},
+ numpages = {16},
+ url = {http://dx.doi.org/10.1109/71.372777},
+ doi = {10.1109/71.372777},
+ acmid = {203132},
+ publisher = {IEEE Press},
+ address = {Piscataway, NJ, USA},
+}
+
+@inproceedings{Oki:1988:VRN:62546.62549,
+ author = {Oki, Brian M. and Liskov, Barbara H.},
+ title = {Viewstamped Replication: A New Primary Copy Method to Support Highly-Available Distributed Systems},
+ series = {PODC '88},
+ year = {1988},
+ isbn = {0-89791-277-2},
+ location = {Toronto, Ontario, Canada},
+ pages = {8--17},
+ numpages = {10},
+ url = {http://doi.acm.org/10.1145/62546.62549},
+ doi = {10.1145/62546.62549},
+ acmid = {62549},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+}
+
+@inproceedings{Ongaro:2014:SUC:2643634.2643666,
+ author = {Ongaro, Diego and Ousterhout, John},
+ title = {In Search of an Understandable Consensus Algorithm},
+ series = {USENIX ATC'14},
+ year = {2014},
+ isbn = {978-1-931971-10-2},
+ location = {Philadelphia, PA},
+ pages = {305--320},
+ numpages = {16},
+ url = {http://dl.acm.org/citation.cfm?id=2643634.2643666},
+ acmid = {2643666},
+ publisher = {USENIX Association},
+ address = {Berkeley, CA, USA},
+}
+
+@article{Lamport:1998:PP:279227.279229,
+ author = {Lamport, Leslie},
+ title = {The Part-time Parliament},
+ journal = {ACM Trans. Comput. Syst.},
+ issue_date = {May 1998},
+ volume = {16},
+ number = {2},
+ month = may,
+ year = {1998},
+ issn = {0734-2071},
+ pages = {133--169},
+ numpages = {37},
+ url = {http://doi.acm.org/10.1145/279227.279229},
+ doi = {10.1145/279227.279229},
+ acmid = {279229},
+ publisher = {ACM},
+ address = {New York, NY, USA},
+ keywords = {state machines, three-phase commit, voting},
+}
\ No newline at end of file
diff --git a/docs/dida_report_latex/latex8.bst b/docs/dida_report_latex/latex8.bst
new file mode 100644
index 0000000..2c7af56
--- /dev/null
+++ b/docs/dida_report_latex/latex8.bst
@@ -0,0 +1,1124 @@
+
+% ---------------------------------------------------------------
+%
+% $Id: latex8.bst,v 1.1 1995/09/15 15:13:49 ienne Exp $
+%
+% by Paolo.Ienne@di.epfl.ch
+%
+
+% ---------------------------------------------------------------
+%
+% no guarantee is given that the format corresponds perfectly to
+% IEEE 8.5" x 11" Proceedings, but most features should be ok.
+%
+% ---------------------------------------------------------------
+%
+% `latex8' from BibTeX standard bibliography style `abbrv'
+% version 0.99a for BibTeX versions 0.99a or later, LaTeX version 2.09.
+% Copyright (C) 1985, all rights reserved.
+% Copying of this file is authorized only if either
+% (1) you make absolutely no changes to your copy, including name, or
+% (2) if you do make changes, you name it something other than
+% btxbst.doc, plain.bst, unsrt.bst, alpha.bst, and abbrv.bst.
+% This restriction helps ensure that all standard styles are identical.
+% The file btxbst.doc has the documentation for this style.
+
+ENTRY
+ { address
+ author
+ booktitle
+ chapter
+ edition
+ editor
+ howpublished
+ institution
+ journal
+ key
+ month
+ note
+ number
+ organization
+ pages
+ publisher
+ school
+ series
+ title
+ type
+ volume
+ year
+ }
+ {}
+ { label }
+
+INTEGERS { output.state before.all mid.sentence after.sentence after.block }
+
+FUNCTION {init.state.consts}
+{ #0 'before.all :=
+ #1 'mid.sentence :=
+ #2 'after.sentence :=
+ #3 'after.block :=
+}
+
+STRINGS { s t }
+
+FUNCTION {output.nonnull}
+{ 's :=
+ output.state mid.sentence =
+ { ", " * write$ }
+ { output.state after.block =
+ { add.period$ write$
+ newline$
+ "\newblock " write$
+ }
+ { output.state before.all =
+ 'write$
+ { add.period$ " " * write$ }
+ if$
+ }
+ if$
+ mid.sentence 'output.state :=
+ }
+ if$
+ s
+}
+
+FUNCTION {output}
+{ duplicate$ empty$
+ 'pop$
+ 'output.nonnull
+ if$
+}
+
+FUNCTION {output.check}
+{ 't :=
+ duplicate$ empty$
+ { pop$ "empty " t * " in " * cite$ * warning$ }
+ 'output.nonnull
+ if$
+}
+
+FUNCTION {output.bibitem}
+{ newline$
+ "\bibitem{" write$
+ cite$ write$
+ "}" write$
+ newline$
+ ""
+ before.all 'output.state :=
+}
+
+FUNCTION {fin.entry}
+{ add.period$
+ write$
+ newline$
+}
+
+FUNCTION {new.block}
+{ output.state before.all =
+ 'skip$
+ { after.block 'output.state := }
+ if$
+}
+
+FUNCTION {new.sentence}
+{ output.state after.block =
+ 'skip$
+ { output.state before.all =
+ 'skip$
+ { after.sentence 'output.state := }
+ if$
+ }
+ if$
+}
+
+FUNCTION {not}
+{ { #0 }
+ { #1 }
+ if$
+}
+
+FUNCTION {and}
+{ 'skip$
+ { pop$ #0 }
+ if$
+}
+
+FUNCTION {or}
+{ { pop$ #1 }
+ 'skip$
+ if$
+}
+
+FUNCTION {new.block.checka}
+{ empty$
+ 'skip$
+ 'new.block
+ if$
+}
+
+FUNCTION {new.block.checkb}
+{ empty$
+ swap$ empty$
+ and
+ 'skip$
+ 'new.block
+ if$
+}
+
+FUNCTION {new.sentence.checka}
+{ empty$
+ 'skip$
+ 'new.sentence
+ if$
+}
+
+FUNCTION {new.sentence.checkb}
+{ empty$
+ swap$ empty$
+ and
+ 'skip$
+ 'new.sentence
+ if$
+}
+
+FUNCTION {field.or.null}
+{ duplicate$ empty$
+ { pop$ "" }
+ 'skip$
+ if$
+}
+
+FUNCTION {emphasize}
+{ duplicate$ empty$
+ { pop$ "" }
+ { "{\em " swap$ * "}" * }
+ if$
+}
+
+INTEGERS { nameptr namesleft numnames }
+
+FUNCTION {format.names}
+{ 's :=
+ #1 'nameptr :=
+ s num.names$ 'numnames :=
+ numnames 'namesleft :=
+ { namesleft #0 > }
+ { s nameptr "{f.~}{vv~}{ll}{, jj}" format.name$ 't :=
+ nameptr #1 >
+ { namesleft #1 >
+ { ", " * t * }
+ { numnames #2 >
+ { "," * }
+ 'skip$
+ if$
+ t "others" =
+ { " et~al." * }
+ { " and " * t * }
+ if$
+ }
+ if$
+ }
+ 't
+ if$
+ nameptr #1 + 'nameptr :=
+
+ namesleft #1 - 'namesleft :=
+ }
+ while$
+}
+
+FUNCTION {format.authors}
+{ author empty$
+ { "" }
+ { author format.names }
+ if$
+}
+
+FUNCTION {format.editors}
+{ editor empty$
+ { "" }
+ { editor format.names
+ editor num.names$ #1 >
+ { ", editors" * }
+ { ", editor" * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.title}
+{ title empty$
+ { "" }
+ { title "t" change.case$ }
+ if$
+}
+
+FUNCTION {n.dashify}
+{ 't :=
+ ""
+ { t empty$ not }
+ { t #1 #1 substring$ "-" =
+ { t #1 #2 substring$ "--" = not
+ { "--" *
+ t #2 global.max$ substring$ 't :=
+ }
+ { { t #1 #1 substring$ "-" = }
+ { "-" *
+ t #2 global.max$ substring$ 't :=
+ }
+ while$
+ }
+ if$
+ }
+ { t #1 #1 substring$ *
+ t #2 global.max$ substring$ 't :=
+ }
+ if$
+ }
+ while$
+}
+
+FUNCTION {format.date}
+{ year empty$
+ { month empty$
+ { "" }
+ { "there's a month but no year in " cite$ * warning$
+ month
+ }
+ if$
+ }
+ { month empty$
+ 'year
+ { month " " * year * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.btitle}
+{ title emphasize
+}
+
+FUNCTION {tie.or.space.connect}
+{ duplicate$ text.length$ #3 <
+ { "~" }
+ { " " }
+ if$
+ swap$ * *
+}
+
+FUNCTION {either.or.check}
+{ empty$
+ 'pop$
+ { "can't use both " swap$ * " fields in " * cite$ * warning$ }
+ if$
+}
+
+FUNCTION {format.bvolume}
+{ volume empty$
+ { "" }
+ { "volume" volume tie.or.space.connect
+ series empty$
+ 'skip$
+ { " of " * series emphasize * }
+ if$
+ "volume and number" number either.or.check
+ }
+ if$
+}
+
+FUNCTION {format.number.series}
+{ volume empty$
+ { number empty$
+ { series field.or.null }
+ { output.state mid.sentence =
+ { "number" }
+ { "Number" }
+ if$
+ number tie.or.space.connect
+ series empty$
+ { "there's a number but no series in " cite$ * warning$ }
+ { " in " * series * }
+ if$
+ }
+ if$
+ }
+ { "" }
+ if$
+}
+
+FUNCTION {format.edition}
+{ edition empty$
+ { "" }
+ { output.state mid.sentence =
+ { edition "l" change.case$ " edition" * }
+ { edition "t" change.case$ " edition" * }
+ if$
+ }
+ if$
+}
+
+INTEGERS { multiresult }
+
+FUNCTION {multi.page.check}
+{ 't :=
+ #0 'multiresult :=
+ { multiresult not
+ t empty$ not
+ and
+ }
+ { t #1 #1 substring$
+ duplicate$ "-" =
+ swap$ duplicate$ "," =
+ swap$ "+" =
+ or or
+ { #1 'multiresult := }
+ { t #2 global.max$ substring$ 't := }
+ if$
+ }
+ while$
+ multiresult
+}
+
+FUNCTION {format.pages}
+{ pages empty$
+ { "" }
+ { pages multi.page.check
+ { "pages" pages n.dashify tie.or.space.connect }
+ { "page" pages tie.or.space.connect }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.vol.num.pages}
+{ volume field.or.null
+ number empty$
+ 'skip$
+ { "(" number * ")" * *
+ volume empty$
+ { "there's a number but no volume in " cite$ * warning$ }
+ 'skip$
+ if$
+ }
+ if$
+ pages empty$
+ 'skip$
+ { duplicate$ empty$
+ { pop$ format.pages }
+ { ":" * pages n.dashify * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.chapter.pages}
+{ chapter empty$
+ 'format.pages
+ { type empty$
+ { "chapter" }
+ { type "l" change.case$ }
+ if$
+ chapter tie.or.space.connect
+ pages empty$
+ 'skip$
+ { ", " * format.pages * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.in.ed.booktitle}
+{ booktitle empty$
+ { "" }
+ { editor empty$
+ { "In " booktitle emphasize * }
+ { "In " format.editors * ", " * booktitle emphasize * }
+ if$
+ }
+ if$
+}
+
+FUNCTION {empty.misc.check}
+
+{ author empty$ title empty$ howpublished empty$
+ month empty$ year empty$ note empty$
+ and and and and and
+ key empty$ not and
+ { "all relevant fields are empty in " cite$ * warning$ }
+ 'skip$
+ if$
+}
+
+FUNCTION {format.thesis.type}
+{ type empty$
+ 'skip$
+ { pop$
+ type "t" change.case$
+ }
+ if$
+}
+
+FUNCTION {format.tr.number}
+{ type empty$
+ { "Technical Report" }
+ 'type
+ if$
+ number empty$
+ { "t" change.case$ }
+ { number tie.or.space.connect }
+ if$
+}
+
+FUNCTION {format.article.crossref}
+{ key empty$
+ { journal empty$
+ { "need key or journal for " cite$ * " to crossref " * crossref *
+ warning$
+ ""
+ }
+ { "In {\em " journal * "\/}" * }
+ if$
+ }
+ { "In " key * }
+ if$
+ " \cite{" * crossref * "}" *
+}
+
+FUNCTION {format.crossref.editor}
+{ editor #1 "{vv~}{ll}" format.name$
+ editor num.names$ duplicate$
+ #2 >
+ { pop$ " et~al." * }
+ { #2 <
+ 'skip$
+ { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" =
+ { " et~al." * }
+ { " and " * editor #2 "{vv~}{ll}" format.name$ * }
+ if$
+ }
+ if$
+ }
+ if$
+}
+
+FUNCTION {format.book.crossref}
+{ volume empty$
+ { "empty volume in " cite$ * "'s crossref of " * crossref * warning$
+ "In "
+ }
+ { "Volume" volume tie.or.space.connect
+ " of " *
+ }
+ if$
+ editor empty$
+ editor field.or.null author field.or.null =
+ or
+ { key empty$
+ { series empty$
+ { "need editor, key, or series for " cite$ * " to crossref " *
+ crossref * warning$
+ "" *
+ }
+ { "{\em " * series * "\/}" * }
+ if$
+ }
+ { key * }
+ if$
+ }
+ { format.crossref.editor * }
+ if$
+ " \cite{" * crossref * "}" *
+}
+
+FUNCTION {format.incoll.inproc.crossref}
+{ editor empty$
+ editor field.or.null author field.or.null =
+ or
+ { key empty$
+ { booktitle empty$
+ { "need editor, key, or booktitle for " cite$ * " to crossref " *
+ crossref * warning$
+ ""
+ }
+ { "In {\em " booktitle * "\/}" * }
+ if$
+ }
+ { "In " key * }
+ if$
+ }
+ { "In " format.crossref.editor * }
+ if$
+ " \cite{" * crossref * "}" *
+}
+
+FUNCTION {article}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ crossref missing$
+ { journal emphasize "journal" output.check
+ format.vol.num.pages output
+ format.date "year" output.check
+ }
+ { format.article.crossref output.nonnull
+ format.pages output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {book}
+{ output.bibitem
+ author empty$
+ { format.editors "author and editor" output.check }
+ { format.authors output.nonnull
+ crossref missing$
+ { "author and editor" editor either.or.check }
+ 'skip$
+ if$
+ }
+ if$
+ new.block
+ format.btitle "title" output.check
+ crossref missing$
+ { format.bvolume output
+ new.block
+ format.number.series output
+ new.sentence
+ publisher "publisher" output.check
+ address output
+ }
+ { new.block
+ format.book.crossref output.nonnull
+ }
+ if$
+ format.edition output
+ format.date "year" output.check
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {booklet}
+{ output.bibitem
+ format.authors output
+ new.block
+ format.title "title" output.check
+ howpublished address new.block.checkb
+ howpublished output
+ address output
+ format.date output
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {inbook}
+{ output.bibitem
+ author empty$
+ { format.editors "author and editor" output.check }
+ { format.authors output.nonnull
+
+ crossref missing$
+ { "author and editor" editor either.or.check }
+ 'skip$
+ if$
+ }
+ if$
+ new.block
+ format.btitle "title" output.check
+ crossref missing$
+ { format.bvolume output
+ format.chapter.pages "chapter and pages" output.check
+ new.block
+ format.number.series output
+ new.sentence
+ publisher "publisher" output.check
+ address output
+ }
+ { format.chapter.pages "chapter and pages" output.check
+ new.block
+ format.book.crossref output.nonnull
+ }
+ if$
+ format.edition output
+ format.date "year" output.check
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {incollection}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ crossref missing$
+ { format.in.ed.booktitle "booktitle" output.check
+ format.bvolume output
+ format.number.series output
+ format.chapter.pages output
+ new.sentence
+ publisher "publisher" output.check
+ address output
+ format.edition output
+ format.date "year" output.check
+ }
+ { format.incoll.inproc.crossref output.nonnull
+ format.chapter.pages output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {inproceedings}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ crossref missing$
+ { format.in.ed.booktitle "booktitle" output.check
+ format.bvolume output
+ format.number.series output
+ format.pages output
+ address empty$
+ { organization publisher new.sentence.checkb
+ organization output
+ publisher output
+ format.date "year" output.check
+ }
+ { address output.nonnull
+ format.date "year" output.check
+ new.sentence
+ organization output
+ publisher output
+ }
+ if$
+ }
+ { format.incoll.inproc.crossref output.nonnull
+ format.pages output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {conference} { inproceedings }
+
+FUNCTION {manual}
+{ output.bibitem
+ author empty$
+ { organization empty$
+ 'skip$
+ { organization output.nonnull
+ address output
+ }
+ if$
+ }
+ { format.authors output.nonnull }
+ if$
+ new.block
+ format.btitle "title" output.check
+ author empty$
+ { organization empty$
+ { address new.block.checka
+ address output
+ }
+ 'skip$
+ if$
+ }
+ { organization address new.block.checkb
+ organization output
+ address output
+ }
+ if$
+ format.edition output
+ format.date output
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {mastersthesis}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ "Master's thesis" format.thesis.type output.nonnull
+ school "school" output.check
+ address output
+ format.date "year" output.check
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {misc}
+{ output.bibitem
+ format.authors output
+ title howpublished new.block.checkb
+ format.title output
+ howpublished new.block.checka
+ howpublished output
+ format.date output
+ new.block
+ note output
+ fin.entry
+ empty.misc.check
+}
+
+FUNCTION {phdthesis}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.btitle "title" output.check
+ new.block
+ "PhD thesis" format.thesis.type output.nonnull
+ school "school" output.check
+ address output
+ format.date "year" output.check
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {proceedings}
+{ output.bibitem
+ editor empty$
+ { organization output }
+ { format.editors output.nonnull }
+
+ if$
+ new.block
+ format.btitle "title" output.check
+ format.bvolume output
+ format.number.series output
+ address empty$
+ { editor empty$
+ { publisher new.sentence.checka }
+ { organization publisher new.sentence.checkb
+ organization output
+ }
+ if$
+ publisher output
+ format.date "year" output.check
+ }
+ { address output.nonnull
+ format.date "year" output.check
+ new.sentence
+ editor empty$
+ 'skip$
+ { organization output }
+ if$
+ publisher output
+ }
+ if$
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {techreport}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ format.tr.number output.nonnull
+ institution "institution" output.check
+ address output
+ format.date "year" output.check
+ new.block
+ note output
+ fin.entry
+}
+
+FUNCTION {unpublished}
+{ output.bibitem
+ format.authors "author" output.check
+ new.block
+ format.title "title" output.check
+ new.block
+ note "note" output.check
+ format.date output
+ fin.entry
+}
+
+FUNCTION {default.type} { misc }
+
+MACRO {jan} {"Jan."}
+
+MACRO {feb} {"Feb."}
+
+MACRO {mar} {"Mar."}
+
+MACRO {apr} {"Apr."}
+
+MACRO {may} {"May"}
+
+MACRO {jun} {"June"}
+
+MACRO {jul} {"July"}
+
+MACRO {aug} {"Aug."}
+
+MACRO {sep} {"Sept."}
+
+MACRO {oct} {"Oct."}
+
+MACRO {nov} {"Nov."}
+
+MACRO {dec} {"Dec."}
+
+MACRO {acmcs} {"ACM Comput. Surv."}
+
+MACRO {acta} {"Acta Inf."}
+
+MACRO {cacm} {"Commun. ACM"}
+
+MACRO {ibmjrd} {"IBM J. Res. Dev."}
+
+MACRO {ibmsj} {"IBM Syst.~J."}
+
+MACRO {ieeese} {"IEEE Trans. Softw. Eng."}
+
+MACRO {ieeetc} {"IEEE Trans. Comput."}
+
+MACRO {ieeetcad}
+ {"IEEE Trans. Comput.-Aided Design Integrated Circuits"}
+
+MACRO {ipl} {"Inf. Process. Lett."}
+
+MACRO {jacm} {"J.~ACM"}
+
+MACRO {jcss} {"J.~Comput. Syst. Sci."}
+
+MACRO {scp} {"Sci. Comput. Programming"}
+
+MACRO {sicomp} {"SIAM J. Comput."}
+
+MACRO {tocs} {"ACM Trans. Comput. Syst."}
+
+MACRO {tods} {"ACM Trans. Database Syst."}
+
+MACRO {tog} {"ACM Trans. Gr."}
+
+MACRO {toms} {"ACM Trans. Math. Softw."}
+
+MACRO {toois} {"ACM Trans. Office Inf. Syst."}
+
+MACRO {toplas} {"ACM Trans. Prog. Lang. Syst."}
+
+MACRO {tcs} {"Theoretical Comput. Sci."}
+
+READ
+
+FUNCTION {sortify}
+{ purify$
+ "l" change.case$
+}
+
+INTEGERS { len }
+
+FUNCTION {chop.word}
+{ 's :=
+ 'len :=
+ s #1 len substring$ =
+ { s len #1 + global.max$ substring$ }
+ 's
+ if$
+}
+
+FUNCTION {sort.format.names}
+{ 's :=
+ #1 'nameptr :=
+ ""
+ s num.names$ 'numnames :=
+ numnames 'namesleft :=
+ { namesleft #0 > }
+ { nameptr #1 >
+ { " " * }
+ 'skip$
+ if$
+ s nameptr "{vv{ } }{ll{ }}{ f{ }}{ jj{ }}" format.name$ 't :=
+ nameptr numnames = t "others" = and
+ { "et al" * }
+ { t sortify * }
+ if$
+ nameptr #1 + 'nameptr :=
+ namesleft #1 - 'namesleft :=
+ }
+ while$
+}
+
+FUNCTION {sort.format.title}
+{ 't :=
+ "A " #2
+ "An " #3
+ "The " #4 t chop.word
+ chop.word
+ chop.word
+ sortify
+ #1 global.max$ substring$
+}
+
+FUNCTION {author.sort}
+{ author empty$
+ { key empty$
+ { "to sort, need author or key in " cite$ * warning$
+ ""
+ }
+ { key sortify }
+ if$
+ }
+ { author sort.format.names }
+ if$
+}
+
+FUNCTION {author.editor.sort}
+{ author empty$
+ { editor empty$
+ { key empty$
+ { "to sort, need author, editor, or key in " cite$ * warning$
+ ""
+ }
+ { key sortify }
+ if$
+ }
+ { editor sort.format.names }
+ if$
+ }
+ { author sort.format.names }
+ if$
+}
+
+FUNCTION {author.organization.sort}
+{ author empty$
+
+ { organization empty$
+ { key empty$
+ { "to sort, need author, organization, or key in " cite$ * warning$
+ ""
+ }
+ { key sortify }
+ if$
+ }
+ { "The " #4 organization chop.word sortify }
+ if$
+ }
+ { author sort.format.names }
+ if$
+}
+
+FUNCTION {editor.organization.sort}
+{ editor empty$
+ { organization empty$
+ { key empty$
+ { "to sort, need editor, organization, or key in " cite$ * warning$
+ ""
+ }
+ { key sortify }
+ if$
+ }
+ { "The " #4 organization chop.word sortify }
+ if$
+ }
+ { editor sort.format.names }
+ if$
+}
+
+FUNCTION {presort}
+{ type$ "book" =
+ type$ "inbook" =
+ or
+ 'author.editor.sort
+ { type$ "proceedings" =
+ 'editor.organization.sort
+ { type$ "manual" =
+ 'author.organization.sort
+ 'author.sort
+ if$
+ }
+ if$
+ }
+ if$
+ " "
+ *
+ year field.or.null sortify
+ *
+ " "
+ *
+ title field.or.null
+ sort.format.title
+ *
+ #1 entry.max$ substring$
+ 'sort.key$ :=
+}
+
+ITERATE {presort}
+
+SORT
+
+STRINGS { longest.label }
+
+INTEGERS { number.label longest.label.width }
+
+FUNCTION {initialize.longest.label}
+{ "" 'longest.label :=
+ #1 'number.label :=
+ #0 'longest.label.width :=
+}
+
+FUNCTION {longest.label.pass}
+{ number.label int.to.str$ 'label :=
+ number.label #1 + 'number.label :=
+ label width$ longest.label.width >
+ { label 'longest.label :=
+ label width$ 'longest.label.width :=
+ }
+ 'skip$
+ if$
+}
+
+EXECUTE {initialize.longest.label}
+
+ITERATE {longest.label.pass}
+
+FUNCTION {begin.bib}
+{ preamble$ empty$
+ 'skip$
+ { preamble$ write$ newline$ }
+ if$
+ "\begin{thebibliography}{" longest.label *
+ "}\setlength{\itemsep}{-1ex}\small" * write$ newline$
+}
+
+EXECUTE {begin.bib}
+
+EXECUTE {init.state.consts}
+
+ITERATE {call.type$}
+
+FUNCTION {end.bib}
+{ newline$
+ "\end{thebibliography}" write$ newline$
+}
+
+EXECUTE {end.bib}
+
+% end of file latex8.bst
+% ---------------------------------------------------------------
+
+
+
diff --git a/docs/dida_report_latex/latex8.sty b/docs/dida_report_latex/latex8.sty
new file mode 100644
index 0000000..ccfee79
--- /dev/null
+++ b/docs/dida_report_latex/latex8.sty
@@ -0,0 +1,169 @@
+
+% ---------------------------------------------------------------
+%
+% $Id: latex8.sty,v 1.2 1995/09/15 15:31:13 ienne Exp $
+%
+% by Paolo.Ienne@di.epfl.ch
+%
+% ---------------------------------------------------------------
+%
+% no guarantee is given that the format corresponds perfectly to
+% IEEE 8.5" x 11" Proceedings, but most features should be ok.
+%
+% ---------------------------------------------------------------
+% with LaTeX2e:
+% =============
+%
+% use as
+% \documentclass[times,10pt,twocolumn]{article}
+% \usepackage{latex8}
+% \usepackage{times}
+%
+% ---------------------------------------------------------------
+
+% with LaTeX 2.09:
+% ================
+%
+% use as
+% \documentstyle[times,art10,twocolumn,latex8]{article}
+%
+% ---------------------------------------------------------------
+% with both versions:
+% ===================
+%
+% specify \pagestyle{empty} to omit page numbers in the final
+% version
+%
+% specify references as
+% \bibliographystyle{latex8}
+% \bibliography{...your files...}
+%
+% use Section{} and SubSection{} instead of standard section{}
+% and subsection{} to obtain headings in the form
+% "1.3. My heading"
+%
+% ---------------------------------------------------------------
+
+\typeout{IEEE 8.5 x 11-Inch Proceedings Style `latex8.sty'.}
+
+% ten point helvetica bold required for captions
+% in some sites the name of the helvetica bold font may differ,
+% change the name here:
+%\font\tenhv = phvb at 10pt
+\font\tenhv = phvb7t at 10pt
+
+% eleven point times bold required for second-order headings
+\font\elvbf = cmbx10 scaled 1100
+%\font\elvbf = ptmb scaled 1100
+
+% set dimensions of columns, gap between columns, and paragraph indent
+\setlength{\textheight}{8.875in}
+\setlength{\textwidth}{6.875in}
+\setlength{\columnsep}{0.3125in}
+\setlength{\topmargin}{0in}
+\setlength{\headheight}{0in}
+\setlength{\headsep}{0in}
+\setlength{\parindent}{1pc}
+\setlength{\oddsidemargin}{-.304in}
+\setlength{\evensidemargin}{-.304in}
+
+% memento from size10.clo
+% \normalsize{\@setfontsize\normalsize\@xpt\@xiipt}
+% \small{\@setfontsize\small\@ixpt{11}}
+% \footnotesize{\@setfontsize\footnotesize\@viiipt{9.5}}
+% \scriptsize{\@setfontsize\scriptsize\@viipt\@viiipt}
+% \tiny{\@setfontsize\tiny\@vpt\@vipt}
+% \large{\@setfontsize\large\@xiipt{14}}
+% \Large{\@setfontsize\Large\@xivpt{18}}
+% \LARGE{\@setfontsize\LARGE\@xviipt{22}}
+% \huge{\@setfontsize\huge\@xxpt{25}}
+% \Huge{\@setfontsize\Huge\@xxvpt{30}}
+
+\def\@maketitle
+ {
+ \newpage
+ \null
+ \vskip .375in
+ \begin{center}
+ {\Large \bf \@title \par}
+ % additional two empty lines at the end of the title
+ \vspace*{24pt}
+ {
+ \large
+ \lineskip .5em
+ \begin{tabular}[t]{c}
+ \@author
+ \end{tabular}
+ \par
+ }
+ % additional small space at the end of the author name
+ \vskip .5em
+ {
+ \large
+ \begin{tabular}[t]{c}
+ \@affiliation
+ \end{tabular}
+ \par
+ \ifx \@empty \@email
+ \else
+ \begin{tabular}{r@{~}l}
+ E-mail: & {\tt \@email}
+ \end{tabular}
+ \par
+ \fi
+ }
+ % additional empty line at the end of the title block
+ \vspace*{12pt}
+ \end{center}
+ }
+
+\def\abstract
+ {%
+ \centerline{\large\bf Abstract}%
+ \vspace*{12pt}%
+ \it%
+ }
+
+\def\endabstract
+ {
+ % additional empty line at the end of the abstract
+ \vspace*{12pt}
+ }
+
+\def\affiliation#1{\gdef\@affiliation{#1}} \gdef\@affiliation{}
+
+\def\email#1{\gdef\@email{#1}}
+\gdef\@email{}
+
+\newlength{\@ctmp}
+\newlength{\@figindent}
+\setlength{\@figindent}{1pc}
+
+\long\def\@makecaption#1#2{
+ \vskip 10pt
+ \setbox\@tempboxa\hbox{\tenhv\noindent #1.~#2}
+ \setlength{\@ctmp}{\hsize}
+ \addtolength{\@ctmp}{-\@figindent}\addtolength{\@ctmp}{-\@figindent}
+ % IF longer than one indented paragraph line
+ \ifdim \wd\@tempboxa >\@ctmp
+ % THEN set as an indented paragraph
+ \begin{list}{}{\leftmargin\@figindent \rightmargin\leftmargin}
+ \item[]\tenhv #1.~#2\par
+ \end{list}
+ \else
+ % ELSE center
+ \hbox to\hsize{\hfil\box\@tempboxa\hfil}
+ \fi}
+
+% correct heading spacing and type
+\def\section{\@startsection {section}{1}{\z@}
+ {14pt plus 2pt minus 2pt}{14pt plus 2pt minus 2pt} {\large\bf}}
+\def\subsection{\@startsection {subsection}{2}{\z@}
+ {13pt plus 2pt minus 2pt}{13pt plus 2pt minus 2pt} {\elvbf}}
+
+% add the period after section numbers
+\newcommand{\Section}[1]{\section{\hskip -1em.~#1}}
+\newcommand{\SubSection}[1]{\subsection{\hskip -1em.~#1}}
+
+% end of file latex8.sty
+% ---------------------------------------------------------------
diff --git a/docs/dida_report_latex/latex8.tex b/docs/dida_report_latex/latex8.tex
new file mode 100644
index 0000000..6dda9fd
--- /dev/null
+++ b/docs/dida_report_latex/latex8.tex
@@ -0,0 +1,509 @@
+
+%
+% $Description: Two variants of a distributed fault-tolerant Tuple Space system$
+%
+% $Author: Mafalda Ferreira, Leonardo Epifânio and Pedro Lopes $
+% $Date: 2018/12/09 15:20:59 $
+% $Revision: 1.0 $
+%
+
+\documentclass[times, 10pt,twocolumn]{article}
+\usepackage{latex8}
+\usepackage{times}
+\usepackage{graphicx}
+\usepackage{framed}
+\usepackage{caption}
+\usepackage{lastpage}
+\usepackage{fancyhdr}
+
+\renewcommand{\headrulewidth}{0pt}
+\fancyhf{}
+\cfoot{\number\value{page} of \pageref{LastPage} pages}
+\pagestyle{fancy}
+
+\graphicspath{ {./images/} }
+
+%-------------------------------------------------------------------------
+% take the % away on next line to produce the final camera-ready version
+\pagestyle{empty}
+
+%-------------------------------------------------------------------------
+\begin{document}
+
+\title{ Two variants of a distributed fault-tolerant Tuple Space system}
+
+\author{Mafalda Ferreira, Leonardo Epif\^{a}nio and Pedro Lopes\\
+Instituto Superior T\'{e}cnico\\
+Av. Rovisco Pais 1, 1049-001 Lisbon\\
+DIDA 18/19 (MEIC-A / MEIC-T / METI)\\
+Group 4
+}
+
+\maketitle
+\thispagestyle{empty}
+
+\begin{abstract}
+ We describe the developed solution for the implementation of two variants
+ of a distributed fault-tolerant Tuple Space system: State Machine Replication~\cite{Bakken:1995:SFP:203121.203132} and Xu-Liskov~\cite{Xu1989ADF}.
+ This paper outlines the key design philosophies and explanation of the
+ algorithms used in each variant. We discuss the relative advantages of each solution,
+ and present experimental results.
+\end{abstract}
+
+%-------------------------------------------------------------------------
+\Section{Introduction} \label{introduction}
+
+A Tuple Space (referred to as TS from this point on) is an approach to shared memory systems, where the processes involved
+communicate indirectly by placing tuples in a TS. A tuple
+can be accessed and removed by pattern matching. Most implementations use a
+centralized solution where the TS is managed by a single server. This is a simple
+solution but has several restrictions. It doesn't scale, being more prone to availability issues, and it is also not fault tolerant, since there's a single point of failure.
+
+Distributed systems are interesting for four main reasons: scalability, high performance,
+availability and reliability. However, implementing a distributed system rises many issues. The computing
+nodes can crash, messages can be lost, duplicated or delivered out of order and the network
+can fail. If we don't take these issues into consideration, the system as a whole can fail.
+
+This paper presents two design variants of a fault-tolerant, distributed system for a
+TS where high availability and fault tolerance are achieved with replication in several nodes.
+The main challenge with replication is maintaining data consistency among replicas. To surpass
+this issue, we consider the two following variants:
+
+\begin{itemize}
+ \item The first variant is called State Machine Replication~\cite{Bakken:1995:SFP:203121.203132}
+ (referred to as SMR from this point on) and it's based on the approach in which the TS
+ behaves like a state machine, maintaining state and changing it deterministically,
+ in response to events received. In order to ensure consistency between replicas, it is required
+ that the replicas must start in the same state and must execute events in the same order.
+ To satisfy the second requirement, we will adopt a total order algorithm.
+
+ \item The second variant is based in the Xu-Liskov implementation~\cite{Xu1989ADF} (referred to as
+ XL from now on) which is designed to minimize delay of responses, given the semantics of the three TS
+ operations: \textit{add}, \textit{read} and \textit{take}. In this approach, the consistency in the \textit{take} operation is assured
+ by the agreement of the replicas on the tuple to be selected, and removing this tuple from all replicas.
+\end{itemize}
+
+We begin in section \ref{design} by briefly describing the implementation tools and the main
+design and architecture of the solution. Section \ref{overview} gives an overview of the common
+parts in the two variants, including leader election, how a replica joins the view, and the perfect and
+imperfect failure detector. Both variants will be described in more detail in sections
+\ref{smr}, \ref{xl} and \ref{client_failure}. In Section \ref{evaluation} we'll discuss the relative advantages of each variant and present experimental results. The conclusion is then presented in section \ref{conclusion}.
+
+%-------------------------------------------------------------------------
+\Section{Design and Architecture} \label{design}
+
+The solution was implemented in C\# and .Net Remoting using Microsoft Visual Studio and the
+OOP paradigm.
+
+%-------------------------------------------------------------------------
+\SubSection{Architecture}
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{dad_architecture.png}
+\caption{Layered architecture of the client and server.}
+\label{fig:architecture}
+\end{figure}
+
+For the client and server we chose a layered architecture and a modular design for simplicity
+in development as we can see in the figure \ref{fig:architecture}.
+
+The module \textit{Message Service Client} has two methods: \textit{Request} and \textit{Multicast}. Both call
+the remote method \textit{Request} of the module \textit{Message Service Server}. The Message Layer receives the messages and passes them to the Protocol Layer where the system protocol runs.
+In the case of the server, the Protocol Layer calls the Tuple Space module to conclude
+the operations over the TS in the replica.
+
+Each layer has a clean API that can be called by the under layer. The requests and
+responses correspond to serialized objects that are processed in the Protocol Layer
+using the Visitor design pattern.
+
+%-------------------------------------------------------------------------
+\Section{Overview} \label{overview}
+Both variants have the following different status: \textsc{Initialization}, \textsc{ViewChange} and
+\textsc{Normal}. The SMR has one extra status, which is \textsc{Recovery}. The server only
+processes requests when in the \textsc{Normal} status. The behaviours in each state are detailed in the succeeding sections.
+
+Despite the differences between the two, they share some of the problems that
+are inherited from the development of distributed systems. All replicas must agree on the
+current view, they must detect failures and, consequentially, must all act the same
+way. Messages can be duplicated, therefore, they must be filtered. Delays can also happen and messages can arrive out of order.
+
+%-------------------------------------------------------------------------
+\SubSection{Filter Duplicate Messages} \label{filter_duplicates}
+Each replica has a Client Table which saves, for each client, the client-id of the last request, the request number
+and the respective response. The algorithm to filter duplicates works as follows:
+\begin{enumerate}
+ \item The client sends the message
+ $\left \langle \textsc{Request} \: \: op, \: c, \: s \right \rangle$ where
+ \textit{op} is the \textit{operation} with its arguments, \textit{c} is the \textit{client-id} and \textit{s} is the \textit{request-number} assigned to the request (which is an incremented counter
+ starting at 0).
+ \item When the server receives the message, it compares the \textit{request-number} with
+ the information in the Client Table (on the row that corresponds to the client that sent it). If the \textit{request-number} is smaller than the
+ \textit{request-number} in the Client Table, it drops it. If it's equal, it re-sends
+ the response that is stored in the Client Table, otherwise it accepts and processes the request.
+\end{enumerate}
+
+%-------------------------------------------------------------------------
+\SubSection{View Changes} \label{view_change}
+Although the information passed in the messages about the replica's state is different for each variant, the semantic of the messages remains. A configuration can be seen as group of $2f + 1$ replicas. The algorithm is as follows:
+
+\begin{enumerate}
+ \item When a replica $i$ notices the need for a view change, it sets its status
+ to \textit{ViewChange} and sends the message
+ $\left \langle \textsc{StartViewChange} \: \: i, \: v, \: c \right \rangle$
+ to all the other replicas, where $v$ is the new \textit{view-number} and $c$ is the new
+ \textit{configuration}.
+
+ \item When a replica $i$ receives $f$ \textsc{StartViewChange} messages for its \textit{view-number}
+ and \textit{configuration}, it sends $\left \langle \textsc{DoViewChange} \: \: i, \: v, \: v', \: c, \: s, \: k \right \rangle$ to the node that will be the leader in the new view, where $v'$ is the previous view-number, $s$ is the state of the replica and $k$ is the commit number.
+
+ \textbf{In SMR:} $s=\left ( l, \: n \right )$, where $l$ is the logger and $n$ is the op-number.
+
+ \textbf{In XL:} $s=\left ( t, \: d \right )$, where $t$ is the TS and $d$ is the Client Table.
+
+ \item When the new leader receives $f + 1$ \textsc{DoViewChange} messages, it selects the best state
+ and updates its state, then informs all the other replicas by sending the message
+ $\left \langle \textsc{StartChange} \: \: i, \: v, \: c, \: s, \: k \right \rangle$
+ and sets its status back to \textit{Normal}. The best state is the one with the biggest view-number.
+ In case of a draw, it selects the biggest op-number and the biggest commit-number in
+ SMR and XL respectively.
+ The replicas that receive the \textsc{StartChange} message, update their state.
+
+\end{enumerate}
+
+In the case of SMR, there are two extra steps:
+\begin{enumerate}
+ \setcounter{enumi}{3}
+ \item The new leader starts accepting client requests. It also executes (in order)
+ any committed operations that it hadn’t executed previously and updates the Client Table.
+
+ \item When the other replicas receive \textsc{StartChange}, they update their state and
+ set the status back to \textit{Normal}. If there are non-committed operations in the log, they
+ send a \textsc{PrepareOk} to the leader. Then execute, in order, all operations known
+ to be commited.
+\end{enumerate}
+
+%-------------------------------------------------------------------------
+\SubSection{Leader Election} \label{leader_election}
+The leader of a configuration is the first server-id in lexicographical order. Thus,
+when a configuration is agreed upon in the view-change, the leader is implicitly elected.
+The leader in SMR also becomes the primary.
+
+%-------------------------------------------------------------------------
+\SubSection{Initialization Operation} \label{init}
+When a replica starts up or thinks that it's out of the current view, the status is changed to \textit{Initialization}. The protocol runs as following:
+\begin{enumerate}
+ \item A replica $i$ sends, to a all possible servers, the message $\left \langle \textsc{ServerHandShake} \: \: i \right \rangle$. If it doesn't receive a response in a determinate timeout,
+ it thinks that it's the first server and sets its status to \textit{Normal}.
+
+ \item If one or more replicas are alive, they will respond with the current view
+ configuration, and replica $i$ sends, to all servers in the
+ configuration, the message $\left \langle \textsc{JoinView} \: \: i, \: u \right \rangle$,
+ where $u$ represents the URL of the replica $i$. Then it waits for a \textsc{StartChange} message
+ and updates its status as explained in section \ref{view_change}.
+ If the message doesn't arrive in a determinate time span, it repeats step 1.
+
+\end{enumerate}
+
+%-------------------------------------------------------------------------
+\SubSection{Failure Detector} \label{failure_detector}
+Each replica $i$ is monitored by all the other replicas in the view. Replica $i$ periodically
+sends a lease renewal request $\left \langle \textsc{HeartBeat} \: \: i \right \rangle$.
+When a monitor acknowledges with $\left \langle \textsc{HeartBeatResponse} \: \: v \right \rangle$,
+where $v$ is the view number%
+\footnote
+ {%
+ The view-number is a health check to see if replica $i$ is in the current view.
+ },
+ replica $i$ is said to obtain a lease.
+
+
+%-------------------------------------------------------------------------
+\subsubsection{Perfect Failure Detector} \label{pfd}
+When a replica $i$ fails to renew a lease in a replica $j$, the perfect failure detector
+says promptly that $i$ is faulty and $j$ sets its status to \textit{ViewChange} and sends the message
+\textsc{StartViewChange} to everyone. The other replicas, as they receive the
+\textsc{StartViewChange} message, change their status to \textit{ViewChange} and also send the message
+\textsc{StartViewChange} to everyone. From here on, the protocol runs as explained in
+section \ref{view_change}. Also, the next leader may not be aware of the detection of
+\textsc{StartViewChange} messages, but when it receives \textsc{DoViewChange}, it changes the status to
+\textit{ViewChange} and the protocol proceeds.
+
+%-------------------------------------------------------------------------
+\subsubsection{Imperfect Failure Detector} \label{ifd}
+When a replica $i$ fails to renew a lease in a replica $j$, the imperfect failure detector
+is conservative. It changes its status to \textit{ViewChange} and proposes a view change by sending
+\textsc{StartViewChange} messages to the other replicas. However, it waits a determinate timeout for $f$ \textsc{StartViewChange} messages, confirming its view change. If it doesn't receive $f$ \textsc{StartViewChange} messages,
+the change is cancelled and the replica changes the status back to \textit{Normal}. There needs to be quorum, to decide that
+$i$ is faulty.
+
+%-------------------------------------------------------------------------
+\Section{State Machine Replication} \label{smr}
+The core of SMR is the implementation of a total order protocol. For this, we decided
+to implement Viewstamped Replication~\cite{Oki:1988:VRN:62546.62549} since it is simple,
+correct, easy to understand and easy to implement in comparison with Paxos~\cite{Lamport:1998:PP:279227.279229}
+or Raft~\cite{Ongaro:2014:SUC:2643634.2643666}.
+
+%-------------------------------------------------------------------------
+\SubSection{Normal Operation} \label{smr_normal}
+\fbox{\begin{minipage}{22em}
+\begin{itemize}
+ \item \textbf{\textit{op-number}:} assigned to the most recently received
+ request, initially 0.
+ \item\textbf{\textit{log}:} This is an array containing \textit{op-number} entries.
+ The entries contain the requests that have been received so far
+ in their assigned order.
+ \item \textbf{\textit{commit-number}:} is the op-number of the most
+ recently committed operation.
+
+\end{itemize}
+\end{minipage}
+}
+\begin{figure}[h]
+ \caption{State of the SMR replica.}
+ \label{fig:state_smr}
+\end{figure}
+
+This section describes the process of requests that are accepted when the primary
+isn't faulty. The client only communicates with the primary when performing a request.
+It runs as follows:
+
+\begin{enumerate}
+ \item When the primary accepts a request, it increments its \textit{op-number}, adds the request
+ to the end of the $log$ and updates the information in the client table. Then it sends
+ a $\left \langle \textsc{Prepare} \: \: v, \: m, \: n, \: k \right \rangle$ to the other
+ replicas, where $m$ is the message received from the client.
+
+ \item The backup replicas process the \textsc{Prepare} messages in order. They won’t accept a prepare message
+ with \textit{op-number} $n$ until they have entries for all earlier requests in their $log$ (it may
+ require \textit{Recovery}), putting the message on hold until the condition is met. Then
+ they increment their \textit{op-number}, add the request to the end of the $log$, update
+ the client's information in the client table, and send a
+ $\left \langle \textsc{PrepareOk} \: \: v, \: n \: i\right \rangle$
+ message to the primary, to indicate that this operation and all earlier ones have been
+ prepared locally.
+
+ \item The primary waits for $f$ \textsc{PrepareOk} messages from different backups.
+ After it receives these messages, it considers the operation (and all earlier ones) to be \textit{committed}.
+ Then, after it has executed all earlier operations (those assigned smaller \textit{op-numbers}),
+ the primary executes the operation by making an upper call to the TS, and increments its \textit{commit-number}.
+ Then, it sends a $\left \langle \textsc{Reply} \: \: v, \: s, \: x\right \rangle$ message
+ to the client, where $v$ is the \textit{view number}, $s$ the \textit{request-number} and $x$ the result
+ of the the call to the TS. The primary saves this reply in the client table.
+
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{images/dad_smr_normal.png}
+\caption{Normal case processing for a configuration f=1.}
+\label{fig:smr_normal_case}
+\end{figure}
+
+ \item The primary informs the other replicas about the commit, by sending a
+ $\left \langle \textsc{Commit} \: \: i, \: v, \: k\right \rangle$.
+
+ \item When a backup learns about the commit it waits until it has the request in its
+ $log$ (which may require Recovery) and until it has executed all earlier operations.
+ Then it executes the operation by doing an upper call to the TS, increments its \textit{commit-number}
+ and updates the client table.
+\end{enumerate}
+
+Figure \ref{fig:smr_normal_case} shows the phases of the normal processing protocol.
+
+
+%-------------------------------------------------------------------------
+\SubSection{Recovery Operation} \label{smr_recovery}
+Recovery is used when a replica lags behind (but hasn't crashed), and needs to be brought up-to-date. After a certain time without executing anything, the
+replica changes its status to \textit{Recovery} and starts the following protocol:
+
+\begin{enumerate}
+ \item The replica $i$ sends a $\left \langle \textsc{Recovery} \: \: i, \: v, \: n', \: k\right \rangle$
+ to all replicas in its configuration, where $n'$ is its \textit{op-number}.
+
+ \item A replica only responds to a \textsc{Recovery} message if, and only if, its status is \textit{Normal}
+ and if it's in the same view. In this case it sends a
+ $\left \langle \textsc{RecoveryResponse} \: \: v, \: n, \: k, \: l'\right \rangle$, where
+ $l'$ corresponds to its $log$ after $n'$.
+
+ \item When replica $i$ receives the \textsc{RecoveryResponse} message, it appends it to
+ its $log$ and updates its state using the other information in the message.
+\end{enumerate}
+%-------------------------------------------------------------------------
+\Section{Xu-Liskov} \label{xl}
+The XL variant is simpler than SMR as it doesn't require total order. However, to maintain
+consistency and simplify the implementation, we decided that the requests of the client
+should be executed in order and, thus, it respects casual order. This means that the add
+operation also blocks, and that it will not return until it gets all the acknowledge messages from the servers
+in the current configuration.
+
+%-------------------------------------------------------------------------
+\SubSection{Normal Operation} \label{xl_normal}
+\fbox{\begin{minipage}{22em}
+\begin{itemize}
+ \item \textbf{\textit{commit-number}:} The number of executed operations.
+
+\end{itemize}
+\end{minipage}
+}
+\begin{figure}[h]
+ \caption{State of the XL replica.}
+ \label{fig:state_xl}
+\end{figure}
+
+Besides the TS and the client table, the XL also has a \textit{commit-number} attribute as shown
+in figure \ref{fig:state_xl}.
+
+The XL variant provides 2 more requests in addition to the three (\textit{add}, \textit{read} and \textit{take}):
+\begin{itemize}
+ \item \textit{\textbf{getAndLock}}: This request has an argument,
+ which is the tuple pattern that the client wants to take. The response will be the set of tuples
+ that match that pattern. The server locks those tuples in the process, atomically. If the lock process
+ fails, it returns a refusal response.
+
+ \item \textit{\textbf{unlock}}: The request \textit{unlock} unlocks any tuple set that the client
+ has locked in the moment. Also atomically.
+
+\end{itemize}
+
+In the \textit{Normal} status, when a replica accepts a request from the client (if the client sends a request
+with a smaller view number, it is dropped), it executes the operation by doing an upper call to
+the TS, advances its commit-number, updates its client table, and sends to the client the message
+$\left \langle \textsc{Reply} \: \: v, \: s, \: x\right \rangle$.
+
+%-------------------------------------------------------------------------
+\Section{Hiding failure from the client} \label{client_failure}
+The biggest advantage in a fault-tolerant system, is masking the failure to the client.
+Here, it is accomplished by also saving the current view status in the client.
+When the client doesn't receive a timely response to a request, it performs a
+\textsc{ClientHandShake} to retrieve the new view and hence, in the case of SMR, retrieving the new leader. The \textsc{ClientHandShake} works as follows:
+\begin{enumerate}
+ \item When the client initializes or doesn't receive a timely response to a request, it sends
+ $\left \langle \textsc{ClientHandShake} \: \: c \right \rangle$ to a set of possible servers,
+ where $c$ is the client-id.
+
+ \item The client waits (with a timeout) until receives a response
+ $\left \langle \textsc{ClientHandShakeResponse} \: \: p, \: v, \: r, \: l \right \rangle$,
+ where $p$ is the protocol used by the server, $v$ is the view-number, $r$ is the configuration
+ and $l$ is leader (in case of SMR). If it doesn't receive the response within the timeout period,
+ step 1 is repeated.
+\end{enumerate}
+
+%-------------------------------------------------------------------------
+\Section{Evaluation} \label{evaluation}
+We evaluate the performance for the different variants of the TS. We identify the relative
+advantages of each implementation and show the practical results of the evaluation.
+
+%-------------------------------------------------------------------------
+\SubSection{Detection: Perfect vs Imperfect}
+In Perfect Failure Detector (PFD), when a replica suspects that a node is down, the other replicas
+trust this judgement and act accordingly. In Imperfect Failure Detector (IFD), there needs to be a quorum
+of replicas suspecting that a node is faulty, in order to agree with a view change.
+
+Although IFD is better at not excluding replicas that are not faulty (but that some of the other replicas think they are),
+it brings a lot more overhead into the system and the network. In our implementation of IFD,
+explained in section \ref{ifd}, a replica can switch to status \textit{ViewChange} several times, halting
+the process of requests each time, before a view-change is accepted by a quorum, leading to a
+decrease in performance in comparison with PFD. And as more replicas join the view, there's a higher probability that this number of view-change requests by a replica increases until it is accepted.
+
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{images/pfd.png}
+\caption{Number of view-change proposals and time until the view change is done in PFD.}
+\label{fig:pfd}
+\end{figure}
+
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{images/ifd.png}
+\caption{Number of view-change proposals and time until the view change is done in IFD.}
+\label{fig:ifd}
+\end{figure}
+
+Figures \ref{fig:pfd} and \ref{fig:ifd} show the number of view-change proposals made
+by a specific replica, when it starts to detect a faulty replica, until the view-change is completed successfully. As we can see, the PFD is pretty much constant, and it scales well with the number of replicas.
+In the IFD, many proposals are made until all the replicas agree at relatively same time. This, however, can take a long
+time or, on the contrary, be very quick. It depends on the circumstances of the asynchronous system. There is a tight correlation between the number of view-change proposals and the time it takes for a view change to be processed successfully:
+the more proposals there are, the more time will pass until the view change is accepted by the quorum.
+
+%-------------------------------------------------------------------------
+\SubSection{SMR vs XL}
+In this section we'll discuss the main advantages of a variant in comparison with the other,
+having in account the idiosyncrasies of each one.
+
+The XL variant takes advantage of the semantics of the operations and improves the performance
+as the \textit{add} and \textit{read} operations can be executed simultaneously. This doesn't
+happen in SMR, where each request is executed at its time in order to guarantee that all replicas execute them
+in the same order. This sometimes results in the halt of the request execution because it didn't execute the previous one,
+which is not necessary in some cases (e.g. when there are two read requests). To verify the validity of this premise, we did a performance test with several clients performing read requests at the same time. We added a random delay between 10ms and 300ms.
+\newline\newline
+\fbox{\begin{minipage}{22em}
+begin-repeat 200\\
+add \textless{}"a",DADTestA(1,"b")\textgreater{}\\
+read \textless{}"*",null\textgreater{}\\
+end-repeat
+\end{minipage}
+}
+\begin{figure}[h]
+\caption{Client script for multiple reads and adds.}
+\label{fig:manyreads_code}
+\end{figure}
+
+Each client performs 400 requests (add and read alternated), and the number of clients are incremented on each iteration (and so are the total
+of requests). We used $f=1$.
+
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{images/manyreads.png}
+\caption{Multiple reads in parallel.}
+\label{fig:manyreads}
+\end{figure}
+
+In figure \ref{fig:manyreads}, although the number of requests is increasing, the XL variant keeps processing
+the read requests almost at the same time. In contrast, in SMR, it takes longer to execute
+as the number of requests increases.
+
+SMR has an advantage in comparison with XL in the take operation. As explained in Xu-Liskov's paper~\cite{Xu1989ADF}, the take operation is like a two-phase commit. A client asks the replicas to lock all the tuples that match the given pattern and the replicas return them. If successful, the client does the intersection of all replicas' responses and, if not empty, selects the tuple to take and broadcasts to every other replica.
+The bottleneck in this approach is the lock operation. The lock fails when it tries to lock an already locked tuple. So, if many clients attempt to take the same tuple pattern, the system will struggle so hard, that it would be better to have total order.
+\newline\newline
+\fbox{\begin{minipage}{22em}
+begin-repeat 50\\
+add \textless{}"a",DADTestA(1,"b")\textgreater{}\\
+end-repeat\\
+begin-repeat 50\\
+take\textless{}"*",null\textgreater{}\\
+end-repeat
+\end{minipage}
+}
+\begin{figure}[h]
+\caption{Client script for multiple take operation.}
+\label{fig:manytakes_code}
+\end{figure}
+
+\begin{figure}[h]
+\centering
+\includegraphics[width=1\linewidth]{images/manytakes.png}
+\caption{Multiple takes in parallel.}
+\label{fig:manytakes}
+\end{figure}
+
+In figure \ref{fig:manytakes} we can see how poorly XL performs when there are multiple
+take requests working with the same tuple set. In this case we used the same tuple, however that was just
+a simple example of the problem. A tuple pattern can generate a huge set of tuples that match. So, even if
+two clients are working with different tuple patterns, there might be a conflict if they sets intersect. In this case, both can fail to acquire a lock to their set.
+
+In SMR, on the other hand, this isn't a problem. The requests are ordered and executed strictly in that agreed sequence, so, there aren't these type of conflicts.
+
+
+%-------------------------------------------------------------------------
+\Section{Conclusion} \label{conclusion}
+This paper presented two approaches for the implementation of a distributed fault-tolerant Tuple Space system. We
+described the used algorithms and their implementation meticulously. In section \ref{evaluation} we verified that each variant's performance depends on the provided workload.
+For systems that require a higher availability, XL might, in general, be a better option, since it doesn't require total order, unlike SMR.
+However, if there is a high amount of removals (\textit{take} requests), SMR may outperform XL, especially if this type of operations are performed on tuple sets that intersect with each other.
+
+Regarding failure detection, PFD will perform better if the network is stable. In the opposite case, where the network's health is impaired by the existence of partitions, big delays, or partial shutdowns, PFD might generate a lot of false positives, and non-faulty servers may wrongly leave the view, compromising the availability. In this case, IFD would be a safer choice.
+
+%-------------------------------------------------------------------------
+\bibliographystyle{latex8}
+\bibliography{latex8}
+
+\end{document}
+
diff --git a/tuple-space/Client/App.config b/tuple-space/Client/App.config
index 00bfd11..db350c7 100644
--- a/tuple-space/Client/App.config
+++ b/tuple-space/Client/App.config
@@ -1,6 +1,6 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Client/Client.cs b/tuple-space/Client/Client.cs
new file mode 100644
index 0000000..cd38eec
--- /dev/null
+++ b/tuple-space/Client/Client.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Linq;
+using Client.ScriptStructure;
+using MessageService;
+using MessageService.Serializable;
+
+namespace Client {
+ public class Client {
+ private const string SERVERS_LIST = "..\\..\\..\\servers.txt";
+
+ public string Id { get; }
+ public Uri Url { get; }
+
+ public int ViewNumber { get; set; }
+ public Uri[] ViewServers { get; set; }
+ public Uri Leader { get; set; }
+
+ public int RequestNumber { get; set; }
+
+ public MessageServiceClient MessageServiceClient { get; set; }
+
+ public Script Script { get; }
+ private readonly Parser parser;
+
+ public Client(string id, Uri url, string scriptFile) {
+ this.Id = id;
+ this.Url = url;
+ this.Script = new Script();
+ this.parser = new Parser();
+ this.SetScript(scriptFile);
+ this.RequestNumber = 0;
+ this.ViewNumber = 0;
+ this.MessageServiceClient = new MessageServiceClient(this.Url);
+ }
+
+ public int GetRequestNumber() {
+ return this.RequestNumber++;
+ }
+
+ private void SetScript(string scriptFile) {
+ string[] lines = System.IO.File.ReadAllLines(@scriptFile); // relative to the executable's folder
+ this.Script.Parse(this.parser, lines, 0);
+ }
+
+ public ClientHandShakeResponse DoHandShake() {
+ // Do the handshake
+ Uri[] servers = System.IO.File.ReadAllLines(SERVERS_LIST).ToList()
+ .ConvertAll(server => new Uri(server))
+ .ToArray();
+
+ IResponses responses = this.MessageServiceClient.RequestMulticast(
+ new ClientHandShakeRequest(this.Id),
+ servers,
+ 1,
+ -1,
+ true);
+ ClientHandShakeResponse response = (ClientHandShakeResponse)responses.ToArray()[0];
+ this.ViewNumber = response.ViewNumber;
+ this.ViewServers = response.ViewConfiguration;
+ this.Leader = response.Leader;
+
+ return response;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Client.csproj b/tuple-space/Client/Client.csproj
index 9107179..b3bc18c 100644
--- a/tuple-space/Client/Client.csproj
+++ b/tuple-space/Client/Client.csproj
@@ -1,53 +1,88 @@
-
-
-
-
- Debug
- AnyCPU
- {D667A288-97A8-447C-8558-ECAE7FF8B3EE}
- Exe
- Client
- Client
- v4.6.1
- 512
- true
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Debug
+ AnyCPU
+ {D667A288-97A8-447C-8558-ECAE7FF8B3EE}
+ Exe
+ Client
+ Client
+ v4.6.1
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {13222cd1-28c6-4869-a653-a11cf61bc95a}
+ MessageService
+
+
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Client/Client.csproj.user b/tuple-space/Client/Client.csproj.user
new file mode 100644
index 0000000..481a24b
--- /dev/null
+++ b/tuple-space/Client/Client.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ 1 tcp://localhost:8101 ../../sample-scripts/sampleClientScript2-1.txt
+
+
\ No newline at end of file
diff --git a/tuple-space/Client/Client.sln b/tuple-space/Client/Client.sln
new file mode 100644
index 0000000..a3145d1
--- /dev/null
+++ b/tuple-space/Client/Client.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client.csproj", "{D667A288-97A8-447C-8558-ECAE7FF8B3EE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D667A288-97A8-447C-8558-ECAE7FF8B3EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D667A288-97A8-447C-8558-ECAE7FF8B3EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D667A288-97A8-447C-8558-ECAE7FF8B3EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D667A288-97A8-447C-8558-ECAE7FF8B3EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/tuple-space/Client/Exceptions/BlockEndMissingException.cs b/tuple-space/Client/Exceptions/BlockEndMissingException.cs
new file mode 100644
index 0000000..587119a
--- /dev/null
+++ b/tuple-space/Client/Exceptions/BlockEndMissingException.cs
@@ -0,0 +1,7 @@
+using System;
+namespace Client.Exceptions {
+ public class BlockEndMissingException : Exception {
+ public BlockEndMissingException(int index) : base("end-repeat keyword is missing: " + (index+1)) {
+ }
+ }
+}
diff --git a/tuple-space/Client/Exceptions/IncorrectCommandException.cs b/tuple-space/Client/Exceptions/IncorrectCommandException.cs
new file mode 100644
index 0000000..b38516a
--- /dev/null
+++ b/tuple-space/Client/Exceptions/IncorrectCommandException.cs
@@ -0,0 +1,7 @@
+using System;
+namespace Client.Exceptions {
+ public class IncorrectCommandException : Exception {
+ public IncorrectCommandException(int index) : base("Incorrect command in line: " + (index+1)) {
+ }
+ }
+}
diff --git a/tuple-space/Client/ListUtils.cs b/tuple-space/Client/ListUtils.cs
new file mode 100644
index 0000000..028756f
--- /dev/null
+++ b/tuple-space/Client/ListUtils.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Client {
+ public static class ListUtils {
+ public static List IntersectLists(List> lists) {
+ List> listsNotEmpty = lists.Where(list => list.Count != 0).ToList();
+
+ if (listsNotEmpty.Count == 0) {
+ return new List();
+ }
+
+ List intersection = listsNotEmpty
+ .Skip(1)
+ .Aggregate(
+ new HashSet(lists.First()),
+ (h, e) => { h.IntersectWith(e); return h; }
+ ).ToList();
+ return intersection;
+ }
+ }
+}
diff --git a/tuple-space/Client/Parser.cs b/tuple-space/Client/Parser.cs
new file mode 100644
index 0000000..f498cfa
--- /dev/null
+++ b/tuple-space/Client/Parser.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Text.RegularExpressions;
+using Client.Exceptions;
+using Client.ScriptStructure;
+
+namespace Client {
+ public class Parser {
+ //original regex expr: < *((("((\*?[a-z]*)|([a-z]*\*?))")|(null)|([A-Za-z_\-]+(\( *((([0-9]+)|("([a-z]*)")) *, *)*((([0-9]+)|("([a-z]*)")) *)\))?)) *, *)*(("((\*?[a-z]*)|([a-z]*\*?))")|(null)|([A-Za-z_\-]+(\( *((([0-9]+)|("([a-z]*)")) *, *)*((([0-9]+)|("([a-z]*)")) *)\))?)) *>
+ private readonly string tupleExpr = "^< *(((\"((\\*?[a-z]*)|([a-z]*\\*?))\")|(null)" +
+ "|([A-Za-z_\\-]+(\\( *((([0-9]+)|(\"([a-z]*)\")) *, *)*((([0-9]+)|(\"(" +
+ "[a-z]*)\")) *)\\))?)) *, *)*((\"((\\*?[a-z]*)|([a-z]*\\*?))\")|(null)" +
+ "|([A-Za-z_\\-]+(\\( *((([0-9]+)|(\"([a-z]*)\")) *, *)*((([0-9]+)|(\"(" +
+ "[a-z]*)\")) *)\\))?)) *>$";
+
+ public int Parse(string[] lines, Block block, int i) {
+ Regex tupleRegex = new Regex(this.tupleExpr);
+ Regex numRegex = new Regex("^[0-9]+$");
+
+ for (; i < lines.Length; i++) {
+
+ if (lines[i].StartsWith("\n") || lines[i].StartsWith("\r\n") || string.IsNullOrWhiteSpace(lines[i])) {
+ continue;
+ }
+ if (lines[i].StartsWith(" ") || lines[i].StartsWith("\t")) {
+ throw new IncorrectCommandException(i);
+ }
+
+ Regex exprRegex = new Regex("(\\s|\\n|\\t|\\r)+");
+ string command = exprRegex.Replace(lines[i], string.Empty);
+
+ if (command.StartsWith("add")) {
+ exprRegex = new Regex("add");
+ string argument = exprRegex.Replace(command, string.Empty, 1);
+ if (!tupleRegex.IsMatch(argument)) {
+ throw new IncorrectCommandException(i);
+ }
+ block.AddNode(new Add(argument));
+
+ } else if (command.StartsWith("read")) {
+ exprRegex = new Regex("read");
+ string argument = exprRegex.Replace(command, string.Empty, 1);
+ if (!tupleRegex.IsMatch(argument)) {
+ throw new IncorrectCommandException(i);
+ }
+ block.AddNode(new Read(argument));
+
+ } else if (command.StartsWith("take")) {
+ exprRegex = new Regex("take");
+ string argument = exprRegex.Replace(command, string.Empty, 1);
+ if (!tupleRegex.IsMatch(argument)) {
+ throw new IncorrectCommandException(i);
+ }
+ block.AddNode(new Take(argument));
+
+ } else if (command.StartsWith("wait")) {
+ exprRegex = new Regex("wait");
+ string argument = exprRegex.Replace(command, string.Empty, 1);
+ if (!numRegex.IsMatch(argument)) {
+ throw new IncorrectCommandException(i);
+ }
+ int num = Int32.Parse(argument);
+ block.AddNode(new Wait(num));
+
+ } else if (command.StartsWith("begin-repeat")) {
+ if (!(block is Script)) {
+ throw new IncorrectCommandException(i);
+ }
+ exprRegex = new Regex("begin-repeat");
+ string argument = exprRegex.Replace(command, string.Empty, 1);
+ if (!numRegex.IsMatch(argument)) {
+ throw new IncorrectCommandException(i);
+ }
+ int num = Int32.Parse(argument);
+
+ RepeatBlock rb = new RepeatBlock(num);
+ i = rb.Parse(this, lines, i + 1);
+ block.AddNode(rb);
+
+ //verify if it reached end (and the loop wasn't closed)
+ if (i == lines.Length) {
+ throw new BlockEndMissingException(i);
+ }
+
+ } else if (command.StartsWith("end-repeat")) {
+ if (!(block is RepeatBlock)) {
+ throw new IncorrectCommandException(i);
+ }
+ exprRegex = new Regex("end-repeat");
+ string remainder = exprRegex.Replace(command, string.Empty, 1);
+ if (remainder != string.Empty) {
+ throw new IncorrectCommandException(i);
+ }
+ return i;
+
+ } else {
+ throw new IncorrectCommandException(i);
+ }
+ }
+ return i;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Program.cs b/tuple-space/Client/Program.cs
index 1ce0499..5f1e8a9 100644
--- a/tuple-space/Client/Program.cs
+++ b/tuple-space/Client/Program.cs
@@ -1,6 +1,47 @@
-namespace Client {
- class Program {
+using System;
+using System.Linq;
+using Client.Exceptions;
+using Client.Visitor;
+
+using MessageService;
+using MessageService.Serializable;
+
+namespace Client {
+ public static class Program {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Program));
+
+
+
static void Main(string[] args) {
+ try {
+ //TODO check arguments
+ Client client = new Client(args[0], new Uri(args[1]), args[2]);
+
+ ClientHandShakeResponse response = client.DoHandShake();
+
+ switch (response.ProtocolUsed) {
+ case Protocol.StateMachineReplication:
+ Log.Info("Handshake: Using State Machine Replication protocol.");
+ client.Script.Accept(new SMRExecuter(client.MessageServiceClient, client));
+ break;
+ case Protocol.XuLiskov:
+ Log.Info("Handshake: Using Xu-Liskov protocol");
+ client.Script.Accept(v: new XLExecuter(client.MessageServiceClient, client));
+ break;
+ default:
+ Log.Fatal("Unknown protocol.");
+ Environment.Exit(1);
+ break;
+ }
+
+ Console.ReadLine();
+ } catch (Exception ex) {
+ if (ex is IncorrectCommandException || ex is BlockEndMissingException) {
+ Console.WriteLine(ex.Message);
+ } else {
+ throw;
+ }
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Properties/AssemblyInfo.cs b/tuple-space/Client/Properties/AssemblyInfo.cs
index baf09d5..9377f5a 100644
--- a/tuple-space/Client/Properties/AssemblyInfo.cs
+++ b/tuple-space/Client/Properties/AssemblyInfo.cs
@@ -13,6 +13,7 @@
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
diff --git a/tuple-space/Client/ScriptStructure/Add.cs b/tuple-space/Client/ScriptStructure/Add.cs
new file mode 100644
index 0000000..45e679f
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Add.cs
@@ -0,0 +1,11 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class Add : Command {
+ public Add(string tuple) : base(tuple) {}
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitAdd(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/BasicNode.cs b/tuple-space/Client/ScriptStructure/BasicNode.cs
new file mode 100644
index 0000000..d60f9b4
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/BasicNode.cs
@@ -0,0 +1,9 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public abstract class BasicNode {
+ public BasicNode() {}
+
+ public abstract void Accept(IBasicVisitor v);
+ }
+}
diff --git a/tuple-space/Client/ScriptStructure/Block.cs b/tuple-space/Client/ScriptStructure/Block.cs
new file mode 100644
index 0000000..ebcf587
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Block.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Client.ScriptStructure {
+ public abstract class Block : BasicNode {
+ protected readonly List nodes;
+
+ private readonly Parser parser;
+
+ public Block() {
+ this.nodes = new List();
+ this.parser = new Parser();
+ }
+
+ public List Nodes => this.nodes;
+
+ public void AddNode(BasicNode node) {
+ this.nodes.Add(node);
+ }
+
+ public int Parse(Parser parser, string[] lines, int index) {
+ return parser.Parse(lines, this, index);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/Command.cs b/tuple-space/Client/ScriptStructure/Command.cs
new file mode 100644
index 0000000..f01c164
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Command.cs
@@ -0,0 +1,11 @@
+namespace Client.ScriptStructure {
+ public abstract class Command : BasicNode {
+ private readonly string tuple;
+
+ public Command(string tuple) {
+ this.tuple = tuple;
+ }
+
+ public string Tuple => this.tuple;
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/Read.cs b/tuple-space/Client/ScriptStructure/Read.cs
new file mode 100644
index 0000000..0a7477b
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Read.cs
@@ -0,0 +1,11 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class Read : Command {
+ public Read(string tuple) : base(tuple) {}
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitRead(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/RepeatBlock.cs b/tuple-space/Client/ScriptStructure/RepeatBlock.cs
new file mode 100644
index 0000000..8c7acfd
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/RepeatBlock.cs
@@ -0,0 +1,17 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class RepeatBlock : Block {
+ private readonly int numRepeats;
+
+ public RepeatBlock(int numRepeats) {
+ this.numRepeats = numRepeats;
+ }
+
+ public int NumRepeats => this.numRepeats;
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitRepeatBlock(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/Script.cs b/tuple-space/Client/ScriptStructure/Script.cs
new file mode 100644
index 0000000..c5aadb1
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Script.cs
@@ -0,0 +1,11 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class Script : Block {
+ public Script() {}
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitScript(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/Take.cs b/tuple-space/Client/ScriptStructure/Take.cs
new file mode 100644
index 0000000..735c817
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Take.cs
@@ -0,0 +1,11 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class Take : Command {
+ public Take(string tuple) : base(tuple) {}
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitTake(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/ScriptStructure/Wait.cs b/tuple-space/Client/ScriptStructure/Wait.cs
new file mode 100644
index 0000000..dcae035
--- /dev/null
+++ b/tuple-space/Client/ScriptStructure/Wait.cs
@@ -0,0 +1,17 @@
+using Client.Visitor;
+
+namespace Client.ScriptStructure {
+ public class Wait : BasicNode {
+ private readonly int time;
+
+ public Wait(int time) {
+ this.time = time;
+ }
+
+ public int Time => this.time;
+
+ public override void Accept(IBasicVisitor v) {
+ v.VisitWait(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Visitor/IBasicVisitor.cs b/tuple-space/Client/Visitor/IBasicVisitor.cs
new file mode 100644
index 0000000..bb52bc9
--- /dev/null
+++ b/tuple-space/Client/Visitor/IBasicVisitor.cs
@@ -0,0 +1,17 @@
+using Client.ScriptStructure;
+
+namespace Client.Visitor {
+ public interface IBasicVisitor {
+ void VisitAdd(Add add);
+
+ void VisitRead(Read read);
+
+ void VisitRepeatBlock(RepeatBlock repeatBlock);
+
+ void VisitScript(Script script);
+
+ void VisitTake(Take take);
+
+ void VisitWait(Wait wait);
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Visitor/SMRExecuter.cs b/tuple-space/Client/Visitor/SMRExecuter.cs
new file mode 100644
index 0000000..3011cbd
--- /dev/null
+++ b/tuple-space/Client/Visitor/SMRExecuter.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Threading;
+
+using Client.ScriptStructure;
+
+using log4net;
+
+using MessageService;
+using MessageService.Serializable;
+using Timeout = MessageService.Timeout;
+
+namespace Client.Visitor {
+ public class SMRExecuter : IBasicVisitor {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(SMRExecuter));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly Client client;
+
+ public SMRExecuter(MessageServiceClient messageServiceClient, Client client) {
+ this.messageServiceClient = messageServiceClient;
+ this.client = client;
+ }
+
+ public void VisitAdd(Add add) {
+ AddRequest addRequest = new AddRequest(this.client.Id, this.client.GetRequestNumber(), add.Tuple);
+
+ ClientResponse clientResponse = null;
+
+ while (clientResponse == null) {
+ clientResponse = (ClientResponse)this.messageServiceClient.Request(
+ addRequest,
+ this.client.Leader,
+ Timeout.TIMEOUT_SMR_CLIENT);
+
+ if (clientResponse != null) {
+ Console.WriteLine($"Added tuple {add.Tuple}");
+ break;
+ }
+
+ this.client.DoHandShake();
+ }
+ }
+
+ public void VisitRead(Read read) {
+ ClientResponse clientResponse;
+ do {
+ clientResponse = null;
+ ReadRequest readRequest = new ReadRequest(this.client.Id, this.client.GetRequestNumber(), read.Tuple);
+ while (clientResponse == null) {
+ clientResponse = (ClientResponse)this.messageServiceClient.Request(
+ readRequest,
+ this.client.Leader,
+ Timeout.TIMEOUT_SMR_CLIENT);
+ if (clientResponse != null) {
+ break;
+ }
+
+ this.client.DoHandShake();
+ }
+
+ if (clientResponse.Result == null) {
+ Thread.Sleep(Timeout.TIMEOUT_SMR_CLIENT_WAIT);
+ }
+ } while (clientResponse.Result == null);
+
+ Console.WriteLine($"Read tuple = {clientResponse.Result}");
+
+ }
+
+ public void VisitTake(Take take) {
+ ClientResponse clientResponse;
+ do {
+ clientResponse = null;
+ TakeRequest readRequest = new TakeRequest(this.client.Id, this.client.GetRequestNumber(), take.Tuple);
+ while (clientResponse == null) {
+ clientResponse = (ClientResponse)this.messageServiceClient.Request(
+ readRequest,
+ this.client.Leader,
+ Timeout.TIMEOUT_SMR_CLIENT);
+ if (clientResponse != null) {
+ break;
+ }
+
+ this.client.DoHandShake();
+ }
+
+ if (clientResponse.Result == null) {
+
+ Thread.Sleep(Timeout.TIMEOUT_SMR_CLIENT_WAIT);
+ }
+ } while (clientResponse.Result == null);
+
+ Console.WriteLine($"Take tuple = {clientResponse.Result}");
+ }
+
+ public void VisitRepeatBlock(RepeatBlock repeatBlock) {
+ int numIterations = 0;
+
+ while (numIterations < repeatBlock.NumRepeats) {
+ foreach (BasicNode node in repeatBlock.Nodes) {
+ node.Accept(this);
+ }
+
+ numIterations++;
+ }
+
+ }
+
+ public void VisitScript(Script script) {
+ foreach (BasicNode node in script.Nodes) {
+ node.Accept(this);
+ }
+ }
+
+ public void VisitWait(Wait wait) {
+ Thread.Sleep(wait.Time);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/Visitor/Writer.cs b/tuple-space/Client/Visitor/Writer.cs
new file mode 100644
index 0000000..a7da5fb
--- /dev/null
+++ b/tuple-space/Client/Visitor/Writer.cs
@@ -0,0 +1,39 @@
+using System;
+using Client.ScriptStructure;
+
+namespace Client.Visitor {
+ public class Writer : IBasicVisitor {
+ public Writer() {}
+
+ public void VisitAdd(Add add) {
+ Console.WriteLine($"add {add.Tuple}");
+ }
+
+ public void VisitRead(Read read) {
+ Console.WriteLine($"read {read.Tuple}");
+ }
+
+ public void VisitRepeatBlock(RepeatBlock repeatBlock) {
+ Console.WriteLine($"begin-repeat {repeatBlock.NumRepeats}");
+ foreach (BasicNode node in repeatBlock.Nodes) {
+ node.Accept(this);
+ }
+ Console.WriteLine("end-repeat");
+ }
+
+ public void VisitScript(Script script) {
+ Console.WriteLine("Script:\n");
+ foreach (BasicNode node in script.Nodes) {
+ node.Accept(this);
+ }
+ }
+
+ public void VisitTake(Take take) {
+ Console.WriteLine($"take {take.Tuple}");
+ }
+
+ public void VisitWait(Wait wait) {
+ Console.WriteLine($"wait {wait.Time}");
+ }
+ }
+}
diff --git a/tuple-space/Client/Visitor/XLExecuter.cs b/tuple-space/Client/Visitor/XLExecuter.cs
new file mode 100644
index 0000000..7327af7
--- /dev/null
+++ b/tuple-space/Client/Visitor/XLExecuter.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Threading;
+using System.Collections.Generic;
+using System.Linq;
+using log4net;
+
+using Client.ScriptStructure;
+using MessageService;
+using MessageService.Serializable;
+using Timeout = MessageService.Timeout;
+
+namespace Client.Visitor {
+ public class XLExecuter : IBasicVisitor {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(SMRExecuter));
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly Client client;
+
+ public XLExecuter(MessageServiceClient messageServiceClient, Client client) {
+ this.messageServiceClient = messageServiceClient;
+ this.client = client;
+ }
+
+ public void VisitAdd(Add add) {
+ AddRequest addRequest = new AddRequest(this.client.ViewNumber, this.client.Id, this.client.GetRequestNumber(), add.Tuple);
+ this.RequestMulticast(addRequest);
+
+ Console.WriteLine($"Added tuple {add.Tuple}");
+ }
+
+ public void VisitRead(Read read) {
+
+ while (true) {
+ ReadRequest readRequest = new ReadRequest(this.client.ViewNumber, this.client.Id, this.client.GetRequestNumber(), read.Tuple);
+
+ IResponse[] responses = this.RequestMulticast(readRequest);
+
+ foreach (IResponse response in responses) {
+ if (response != null && ((ClientResponse)response).Result != null) {
+ Console.WriteLine($"Read tuple = {((ClientResponse)response).Result}");
+ return;
+ }
+ }
+
+ Thread.Sleep(Timeout.TIMEOUT_XL_CLIENT_WAIT);
+ }
+
+ }
+
+ public void VisitTake(Take take) {
+ string tupleToTake;
+ // First phase: Lock and choose a tuple from intersection
+ while (true) {
+ IResponse[] responses = this.GetAndLock(take);
+
+ List> intersection = new List>();
+ foreach (IResponse response in responses) {
+ if (response != null && ((GetAndLockResponse)response).Tuples.Count > 0) {
+ intersection.Add(((GetAndLockResponse)response).Tuples);
+ }
+ }
+ List intersectTuples = ListUtils.IntersectLists(intersection);
+
+ if (intersectTuples.Count <= 0) {
+ UnlockRequest unlockRequest = new UnlockRequest(
+ this.client.ViewNumber,
+ this.client.Id,
+ this.client.GetRequestNumber());
+ this.messageServiceClient.RequestMulticast(
+ unlockRequest,
+ this.client.ViewServers,
+ this.client.ViewServers.Length,
+ -1,
+ false);
+ Log.Debug("Take intersection is empty. Needs to be requested again.");
+ Thread.Sleep(Timeout.TIMEOUT_XL_CLIENT_WAIT);
+ } else {
+ tupleToTake = intersectTuples[0];
+ break;
+ }
+ }
+
+ // Second phase: Take
+ TakeRequest takeRequest = new TakeRequest(
+ this.client.ViewNumber,
+ this.client.Id,
+ this.client.GetRequestNumber(),
+ tupleToTake);
+ this.RequestMulticast(takeRequest);
+ Console.WriteLine($"Take tuple = {tupleToTake}");
+ }
+
+ public void VisitRepeatBlock(RepeatBlock repeatBlock) {
+ int numIterations = 0;
+
+ while (numIterations < repeatBlock.NumRepeats) {
+ foreach (BasicNode node in repeatBlock.Nodes) {
+ node.Accept(this);
+ }
+
+ numIterations++;
+ }
+
+ }
+
+ public void VisitScript(Script script) {
+ foreach (BasicNode node in script.Nodes) {
+ node.Accept(this);
+ }
+ }
+
+ public void VisitWait(Wait wait) {
+ Thread.Sleep(wait.Time);
+ }
+
+ private IResponse[] RequestMulticast(ClientRequest clientRequest) {
+ IResponse[] responses = { };
+ while (responses.Length != this.client.ViewServers.Length) {
+ responses = this.messageServiceClient.RequestMulticast(
+ clientRequest,
+ this.client.ViewServers,
+ this.client.ViewServers.Length,
+ Timeout.TIMEOUT_XL_CLIENT,
+ true).ToArray();
+
+ if (responses.Length != this.client.ViewServers.Length) {
+ this.client.DoHandShake();
+ clientRequest.ViewNumber = this.client.ViewNumber;
+ }
+ }
+
+ return responses;
+ }
+
+ private IResponse[] GetAndLock(Take take) {
+ while (true) {
+ int getAndLockRequestNumber = this.client.GetRequestNumber();
+ int unlockRequestNumber = 0;
+ GetAndLockRequest getAndLockRequest = new GetAndLockRequest(
+ this.client.ViewNumber,
+ this.client.Id,
+ getAndLockRequestNumber,
+ take.Tuple);
+ IResponse[] responses = { };
+
+ while (this.client.ViewServers.Length != responses.Length) {
+ int numberOfLockedRequests = this.client.ViewServers.Length;
+ Uri[] servers = this.client.ViewServers;
+ responses = this.messageServiceClient.RequestMulticast(
+ getAndLockRequest,
+ servers,
+ numberOfLockedRequests,
+ Timeout.TIMEOUT_XL_CLIENT,
+ true).ToArray();
+
+ if (responses.Length != this.client.ViewServers.Length) {
+ this.client.DoHandShake();
+ getAndLockRequest.ViewNumber = this.client.ViewNumber;
+ }
+
+ if (responses.Length == 0) {
+ continue;
+ }
+
+ // Check if refused
+ // No-one refused
+ if (responses.Length == numberOfLockedRequests) {
+ return responses;
+ }
+
+ // Some refused but the majority locked
+ if (responses.Length >= (numberOfLockedRequests / 2) + 1) {
+ continue;
+ }
+
+ if (this.client.RequestNumber == getAndLockRequestNumber) {
+ unlockRequestNumber = this.client.GetRequestNumber();
+ }
+ // The majority didn't lock so it's needed to unlock
+ UnlockRequest unlockRequest = new UnlockRequest(
+ this.client.ViewNumber,
+ this.client.Id,
+ unlockRequestNumber);
+
+ // Unlock
+ this.messageServiceClient.RequestMulticast(
+ unlockRequest,
+ servers,
+ numberOfLockedRequests,
+ -1,
+ false);
+ break;
+ }
+
+ return responses;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Client/log4net.config.xml b/tuple-space/Client/log4net.config.xml
new file mode 100644
index 0000000..6858ef9
--- /dev/null
+++ b/tuple-space/Client/log4net.config.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Client/packages.config b/tuple-space/Client/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/Client/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Client/sample-scripts/sampleClientScript.txt b/tuple-space/Client/sample-scripts/sampleClientScript.txt
new file mode 100644
index 0000000..6e6c3d5
--- /dev/null
+++ b/tuple-space/Client/sample-scripts/sampleClientScript.txt
@@ -0,0 +1,11 @@
+add <"a",DADTestA(1,"b")>
+begin-repeat 3
+read <"*",null>
+read <"a*",DADTestA>
+read <"*a",DADTestA(1,"b")>
+end-repeat
+
+add <"xx",DADTestB(1,"c",2)>
+take <"x*",DADTestB>
+add <"xx",DADTestC(1,"b","d")>
+read <"*a",DADTestA(1,"b")>
diff --git a/tuple-space/Client/sample-scripts/sampleClientScript1.txt b/tuple-space/Client/sample-scripts/sampleClientScript1.txt
new file mode 100644
index 0000000..b2a5510
--- /dev/null
+++ b/tuple-space/Client/sample-scripts/sampleClientScript1.txt
@@ -0,0 +1,18 @@
+add <"a">
+wait 1000
+read <"a">
+wait 500
+read <"a*">
+wait 1000
+add <"a","b","c","d","e">
+take <"*","*","*","*","*">
+wait 1000
+add
+take
+wait 1000
+begin-repeat 3
+add <"xx",DADTestB(10,"c",20)>
+wait 1000
+take <"x*",DADTestB>
+end-repeat
+
diff --git a/tuple-space/Client/sample-scripts/sampleClientScript2-1.txt b/tuple-space/Client/sample-scripts/sampleClientScript2-1.txt
new file mode 100644
index 0000000..b35cc29
--- /dev/null
+++ b/tuple-space/Client/sample-scripts/sampleClientScript2-1.txt
@@ -0,0 +1,4 @@
+begin-repeat 100
+add <"a">
+take <"b">
+end-repeat
\ No newline at end of file
diff --git a/tuple-space/Client/sample-scripts/sampleClientScript2-2.txt b/tuple-space/Client/sample-scripts/sampleClientScript2-2.txt
new file mode 100644
index 0000000..3a37267
--- /dev/null
+++ b/tuple-space/Client/sample-scripts/sampleClientScript2-2.txt
@@ -0,0 +1,6 @@
+wait 5000
+begin-repeat 100
+add <"b">
+take <"a">
+end-repeat
+
diff --git a/tuple-space/MessageService/Constants.cs b/tuple-space/MessageService/Constants.cs
new file mode 100644
index 0000000..4d22fc7
--- /dev/null
+++ b/tuple-space/MessageService/Constants.cs
@@ -0,0 +1,5 @@
+namespace MessageService {
+ public static class Constants {
+ public static readonly string MESSAGE_SERVICE_NAME = "MessageService";
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/ExecutorsDataObjects.cs b/tuple-space/MessageService/ExecutorsDataObjects.cs
new file mode 100644
index 0000000..d7bc7d1
--- /dev/null
+++ b/tuple-space/MessageService/ExecutorsDataObjects.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using MessageService.Serializable;
+
+namespace MessageService {
+ public interface IExecutorVisitor {
+ void ExecuteAdd(AddExecutor addExecutor);
+
+ void ExecuteTake(TakeExecutor takeExecutor);
+
+ void ExecuteRead(ReadExecutor readExecutor);
+ }
+
+ public interface IExecutorXLVisitor : IExecutorVisitor{
+ void ExecuteGetAndLock(GetAndLockExecutor getAndLockExecutor);
+
+ void ExecuteUnlock(UnlockExecutor unlockExecutor);
+ }
+
+ [Serializable]
+ public abstract class Executor : ClientResponse {
+ [NonSerialized] private AutoResetEvent executed;
+
+ public string ClientId { get; set; }
+ public string Tuple { get; set; }
+ public new int RequestNumber { get; set; }
+ public int OpNumber { get; set; }
+ public AutoResetEvent Executed {
+ get => this.executed;
+ set => this.executed = value;
+ }
+ public bool AddedToQueue { get; set; }
+
+ protected Executor(ClientRequest clientRequest) {
+ this.ClientId = clientRequest.ClientId;
+ this.Tuple = clientRequest.Tuple;
+ this.RequestNumber = clientRequest.RequestNumber;
+ this.Executed = new AutoResetEvent(false);
+ this.AddedToQueue = false;
+ }
+
+ protected Executor(ClientRequest clientRequest, int opNumber) {
+ this.ClientId = clientRequest.ClientId;
+ this.Tuple = clientRequest.Tuple;
+ this.RequestNumber = clientRequest.RequestNumber;
+ this.OpNumber = opNumber;
+ this.Executed = new AutoResetEvent(false);
+ this.AddedToQueue = false;
+ }
+
+ public abstract void Execute(IExecutorVisitor visitor);
+ }
+
+ [Serializable]
+ public class AddExecutor : Executor {
+ public AddExecutor(ClientRequest clientRequest) : base(clientRequest) { }
+
+ public AddExecutor(ClientRequest clientRequest, int opNumber)
+ : base(clientRequest, opNumber) { }
+
+ public override void Execute(IExecutorVisitor visitor) {
+ visitor.ExecuteAdd(this);
+ }
+ }
+
+ [Serializable]
+ public class TakeExecutor : Executor {
+ public TakeExecutor(ClientRequest clientRequest) : base(clientRequest) { }
+
+ public TakeExecutor(ClientRequest clientRequest, int opNumber)
+ : base(clientRequest, opNumber) { }
+
+ public override void Execute(IExecutorVisitor visitor) {
+ visitor.ExecuteTake(this);
+ }
+ }
+
+ [Serializable]
+ public class ReadExecutor : Executor {
+ public ReadExecutor(ClientRequest clientRequest) : base(clientRequest) { }
+
+ public ReadExecutor(ClientRequest clientRequest, int opNumber) : base(clientRequest, opNumber) { }
+
+ public override void Execute(IExecutorVisitor visitor) {
+ visitor.ExecuteRead(this);
+ }
+ }
+
+ public static class ExecutorFactory {
+ private static readonly ConcurrentDictionary Executors = new ConcurrentDictionary();
+
+ public static Executor Factory(ClientRequest clientRequest, int opNumber) {
+ Executor clientExecutor;
+ lock (clientRequest) {
+ if (Executors.TryGetValue(clientRequest, out clientExecutor)) {
+ return clientExecutor;
+ }
+ }
+
+ if (clientRequest is AddRequest) {
+ clientExecutor = new AddExecutor(clientRequest, opNumber);
+ } else if (clientRequest is TakeRequest) {
+ clientExecutor = new TakeExecutor(clientRequest, opNumber);
+ } else if (clientRequest is ReadRequest) {
+ clientExecutor = new ReadExecutor(clientRequest, opNumber);
+ }
+
+ Executors.TryAdd(clientRequest, clientExecutor);
+ return clientExecutor;
+ }
+ }
+
+ // XL --------------------------------------------------------------------------------
+ [Serializable]
+ public abstract class ExecutorXL : Executor {
+ protected ExecutorXL(ClientRequest clientRequest)
+ : base(clientRequest) { }
+ }
+
+ [Serializable]
+ public class GetAndLockExecutor : Executor {
+ public GetAndLockExecutor(ClientRequest clientRequest) : base(clientRequest) { }
+
+ public override void Execute(IExecutorVisitor visitor) {
+ (visitor as IExecutorXLVisitor)?.ExecuteGetAndLock(this);
+
+ }
+ }
+
+ [Serializable]
+ public class UnlockExecutor : Executor {
+ public UnlockExecutor(ClientRequest clientRequest) : base(clientRequest) { }
+
+ public override void Execute(IExecutorVisitor visitor) {
+ (visitor as IExecutorXLVisitor)?.ExecuteUnlock(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/GlobalSuppressions.cs b/tuple-space/MessageService/GlobalSuppressions.cs
new file mode 100644
index 0000000..4e14ff7
--- /dev/null
+++ b/tuple-space/MessageService/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "", Scope = "type", Target = "~T:MessageService.Visitor.IMessageSMRVisitor")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S1450:Private fields only used as local variables in methods should become local variables", Justification = "", Scope = "member", Target = "~F:MessageService.MessageServiceClient.channel")]
\ No newline at end of file
diff --git a/tuple-space/MessageService/Interfaces.cs b/tuple-space/MessageService/Interfaces.cs
new file mode 100644
index 0000000..1a86e4a
--- /dev/null
+++ b/tuple-space/MessageService/Interfaces.cs
@@ -0,0 +1,64 @@
+using System;
+
+using MessageService.Visitor;
+
+namespace MessageService {
+ ///
+ /// Remoting interface
+ ///
+ public interface IMessageServiceServer {
+ ///
+ /// Processes the request.
+ ///
+ IResponse Request(IMessage message);
+ }
+
+ ///
+ /// Service wrapper, makes calls to IMessageServiceServer
+ ///
+ public interface IMessageServiceClient {
+ ///
+ /// Makes a simple request call
+ ///
+ IResponse Request(IMessage message, Uri url);
+
+ ///
+ /// Makes a request call, returns when timeouts..
+ ///
+ IResponse Request(IMessage message, Uri url, int timeout);
+
+ ///
+ /// Multicasts a request. Waits for and returns.
+ /// If is less than zero, it waits for all.
+ /// If is less than zero it waits indefinitely.
+ /// If is true, then it only counts messages that are not null.
+ ///
+ IResponses RequestMulticast(IMessage message, Uri[] urls, int numberResponsesToWait, int timeout, bool notNull);
+ }
+
+ public interface IMessage {
+ IResponse Accept(IMessageSMRVisitor visitor);
+
+ IResponse Accept(IMessageXLVisitor visitor);
+ }
+
+ public interface IResponse { }
+
+ public interface IResponses {
+ void Add(IResponse response);
+
+ IResponse[] ToArray();
+
+ int Count();
+ }
+
+ public interface IProtocol {
+ IResponse ProcessRequest(IMessage message);
+
+ void Init(MessageServiceClient messageServiceClient, Uri url, string serverId);
+
+ string Status();
+
+ bool QueueWhenFrozen();
+ }
+}
diff --git a/tuple-space/MessageService/MessageService.csproj b/tuple-space/MessageService/MessageService.csproj
new file mode 100644
index 0000000..e32d94f
--- /dev/null
+++ b/tuple-space/MessageService/MessageService.csproj
@@ -0,0 +1,71 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {13222CD1-28C6-4869-A653-A11CF61BC95A}
+ Library
+ Properties
+ MessageService
+ MessageService
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {F3FB05ED-40A7-4C96-B94B-B926893CFF7C}
+ TupleSpace
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/MessageService/MessageServiceClient.cs b/tuple-space/MessageService/MessageServiceClient.cs
new file mode 100644
index 0000000..82fe2ae
--- /dev/null
+++ b/tuple-space/MessageService/MessageServiceClient.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.Remoting.Channels;
+using System.Runtime.Remoting.Channels.Tcp;
+using System.Threading;
+using System.Threading.Tasks;
+
+using log4net;
+
+namespace MessageService {
+
+ public class MessageServiceClient : IMessageServiceClient {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(IMessageServiceClient));
+
+ private readonly TcpChannel channel;
+
+ private bool frozen;
+ private EventWaitHandle freezeHandler;
+
+ public MessageServiceClient(Uri myUrl) {
+ this.frozen = false;
+ this.freezeHandler = new EventWaitHandle(false, EventResetMode.ManualReset);
+
+ //create tcp channel
+ this.channel = new TcpChannel(myUrl.Port);
+ ChannelServices.RegisterChannel(this.channel, false);
+ Log.Info($"TCP channel created at {myUrl.Host}:{myUrl.Port}.");
+ }
+
+ public MessageServiceClient(TcpChannel channel) {
+ this.frozen = false;
+ this.freezeHandler = new EventWaitHandle(false, EventResetMode.ManualReset);
+
+ this.channel = channel;
+ }
+
+ public IResponse Request(IMessage message, Uri url) {
+ // block if frozen
+ this.BlockFreezeState(message);
+ try {
+ Log.Debug($"Request called with parameters: message: {message}, url: {url}");
+ MessageServiceServer server = GetRemoteMessageService(url);
+ if (server != null) {
+ return server.Request(message);
+ }
+
+ Log.Error($"Request: Could not resolve url {url.Host}:{url.Port}");
+ return null;
+ } catch (Exception e) {
+ Log.Error(e.Message);
+ return null;
+ }
+ }
+
+ public IResponse Request(IMessage message, Uri url, int timeout) {
+ // block if frozen
+ this.BlockFreezeState(message);
+
+ Log.Debug($"Request called with parameters: message: {message}, url: {url}, timeout: {timeout}");
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+
+ // Create Task and run async
+ Task task = Task.Factory.StartNew(() => {
+ using (cancellationTokenSource.Token.Register(() => Log.Warn("Request: Task cancellation was issued."))) {
+ return this.Request(message, url);
+ }
+ }, cancellationTokenSource.Token);
+ try {
+ bool taskCompleted = timeout < 0 ? task.Wait(-1) : task.Wait(timeout);
+
+ if (taskCompleted) {
+ return task.Result;
+ }
+ cancellationTokenSource.Cancel();
+ } catch (Exception e) {
+ Log.Error(e.Message);
+ }
+
+ Log.Error("Request: Timeout, abort thread request.");
+ return null;
+ }
+
+ public IResponses RequestMulticast(
+ IMessage message,
+ Uri[] urls,
+ int numberResponsesToWait,
+ int timeout,
+ bool notNull) {
+ // block if frozen
+ this.BlockFreezeState(message);
+
+ if (urls.Length == 0) {
+ return new Responses();
+ }
+
+ Log.Debug($"Multicast Request called with parameters: message: {message}, url: {urls},"
+ + $"numberResponsesToWait: {numberResponsesToWait}, timeout: {timeout}");
+
+ if (numberResponsesToWait > urls.Length) {
+ return new Responses();
+ } else if (numberResponsesToWait < 0) {
+ numberResponsesToWait = urls.Length;
+ }
+
+ List> tasks = new List>();
+ List cancellations = new List();
+
+ foreach (Uri url in urls) {
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+
+ // Create Task and run async
+
+ Task task = Task.Factory.StartNew(() => {
+ using (cancellationTokenSource.Token.Register(() => Log.Debug("Task cancellation requested"))) {
+ return this.Request(message, url);
+ }
+ }, cancellationTokenSource.Token);
+
+ lock (tasks) {
+ tasks.Add(task);
+ }
+
+ lock (cancellations) {
+ cancellations.Add(cancellationTokenSource);
+ }
+
+ }
+
+ // Wait until numberResponsesToWait
+ IResponses responses = new Responses();
+ CancellationTokenSource cancellationTs = new CancellationTokenSource();
+ Task getResponses = Task.Factory.StartNew(
+ () => { using (cancellationTs.Token.Register(() => { MessageServiceClient.CancelSubTasks(cancellations); })) {
+ GetResponses(responses, numberResponsesToWait, tasks, cancellations, notNull);
+ }
+ },
+ cancellationTs.Token);
+
+ try {
+ bool taskCompleted = timeout < 0 ? getResponses.Wait(-1) : getResponses.Wait(timeout);
+
+ if (taskCompleted) {
+ Log.Debug($"Multicast response: {responses}");
+ return responses;
+ }
+
+ cancellationTs.Cancel();
+ } catch (Exception e) {
+ Log.Error(e.Message);
+ }
+
+ Log.Error("Multicast Request: Timeout, abort thread request.");
+ return responses;
+ }
+
+ private static void GetResponses(
+ IResponses responses,
+ int numberResponsesToWait,
+ List> tasks,
+ List cancellations,
+ bool notNull) {
+
+ try {
+ int countMessages = 0;
+ while (countMessages < numberResponsesToWait) {
+ int index = Task.WaitAny(tasks.ToArray());
+ if (index < 0) {
+ return;
+ }
+ if (notNull) {
+ if (tasks[index].Result != null) {
+ lock (responses) {
+ responses.Add(tasks[index].Result);
+ countMessages++;
+ }
+ }
+ } else {
+ lock (responses) {
+ responses.Add(tasks[index].Result);
+ countMessages++;
+ }
+ }
+
+ lock (cancellations) {
+ // cancel task
+ cancellations[index].Cancel();
+ cancellations.RemoveAt(index);
+ }
+
+ lock (tasks) {
+ tasks.RemoveAt(index);
+ }
+ }
+
+ // Cancel remaining sub-tasks
+ MessageServiceClient.CancelSubTasks(cancellations);
+ } catch (Exception e) {
+ Log.Error(e.Message);
+ }
+
+ }
+
+ private static void CancelSubTasks(List cancellations) {
+ Log.Warn("Multicast Request: cancellation was issued. Cancel all request Tasks.");
+ // cancel all other tasks
+ lock (cancellations) {
+ try {
+ foreach (CancellationTokenSource cancellationTokenSource in cancellations) {
+ cancellationTokenSource.Cancel();
+ }
+ } catch (Exception e) {
+ Log.Error(e.Message);
+ }
+ }
+ }
+
+ private static MessageServiceServer GetRemoteMessageService(Uri url) {
+ string serviceUrl = $"tcp://{url.Host}:{url.Port}/{Constants.MESSAGE_SERVICE_NAME}";
+ Log.Debug($"Activate service at {serviceUrl}");
+ return (MessageServiceServer) Activator.GetObject(
+ typeof(MessageServiceServer),
+ serviceUrl);
+ }
+
+ public void Freeze() {
+ this.frozen = true;
+ }
+
+ public void Unfreeze() {
+ this.frozen = false;
+ this.freezeHandler.Set();
+ this.freezeHandler.Reset();
+ }
+
+ private void BlockFreezeState(IMessage message) {
+ while (frozen) {
+ this.freezeHandler.WaitOne();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/MessageServiceServer.cs b/tuple-space/MessageService/MessageServiceServer.cs
new file mode 100644
index 0000000..97cf27a
--- /dev/null
+++ b/tuple-space/MessageService/MessageServiceServer.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.Remoting;
+using System.Threading;
+
+using log4net;
+
+namespace MessageService {
+ public class MessageServiceServer : MarshalByRefObject, IMessageServiceServer {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(IMessageServiceServer));
+
+ private readonly IProtocol protocol;
+ private readonly int minDelay;
+ private readonly int maxDelay;
+
+ private readonly Random seedRandom;
+
+ private bool frozen;
+ private int frozenRequests;
+
+ private int IncrementFrozenRequests() { return Interlocked.Increment(ref this.frozenRequests); }
+ private int DecrementFrozenRequests() { return Interlocked.Decrement(ref this.frozenRequests); }
+ private readonly EventWaitHandle frozenRequestsHandler;
+
+ private readonly EventWaitHandle handler;
+
+
+ public MessageServiceServer(IProtocol protocol, int minDelay, int maxDelay) {
+ this.protocol = protocol;
+ this.minDelay = minDelay;
+ this.maxDelay = maxDelay;
+ this.frozen = false;
+ this.handler = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.frozenRequestsHandler = new EventWaitHandle(false, EventResetMode.ManualReset);
+
+ this.seedRandom = new Random();
+
+ // Register remote
+ RemotingServices.Marshal(
+ this,
+ Constants.MESSAGE_SERVICE_NAME,
+ typeof(MessageServiceServer));
+ }
+
+ public IResponse Request(IMessage message) {
+ int delay = this.seedRandom.Next(this.minDelay, this.maxDelay);
+
+ Log.Debug($"Request (Process Delay = {delay} ms) with parameters: message: {message}");
+
+ Thread.Sleep(delay);
+
+ IResponse response = null;
+ if (this.protocol.QueueWhenFrozen()) {
+ if (frozen) {
+ this.IncrementFrozenRequests();
+ while (this.frozen) {
+ this.handler.WaitOne();
+ }
+
+ response = this.protocol.ProcessRequest(message);
+
+ this.DecrementFrozenRequests();
+ this.frozenRequestsHandler.Set();
+ this.frozenRequestsHandler.Reset();
+ } else {
+ while (this.frozenRequests > 0) {
+ this.frozenRequestsHandler.WaitOne();
+ }
+
+ response = this.protocol.ProcessRequest(message);
+ }
+ } else {
+ while (this.frozen) {
+ this.handler.WaitOne();
+ }
+
+ response = this.protocol.ProcessRequest(message);
+ }
+
+ Log.Debug($"Response: {response}");
+ return response;
+ }
+
+ public void Freeze() {
+ frozenRequests = 0;
+ this.frozen = true;
+ }
+
+ public void Unfreeze() {
+ this.frozen = false;
+ this.handler.Set();
+ this.handler.Reset();
+ }
+
+ public override object InitializeLifetimeService() {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Properties/AssemblyInfo.cs b/tuple-space/MessageService/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cc5c280
--- /dev/null
+++ b/tuple-space/MessageService/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MessageServiceClient")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MessageServiceClient")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("13222cd1-28c6-4869-a653-a11cf61bc95a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/MessageService/Serializable/ClientRequestMessages.cs b/tuple-space/MessageService/Serializable/ClientRequestMessages.cs
new file mode 100644
index 0000000..0d4a3f1
--- /dev/null
+++ b/tuple-space/MessageService/Serializable/ClientRequestMessages.cs
@@ -0,0 +1,126 @@
+using System;
+using MessageService.Visitor;
+
+namespace MessageService.Serializable {
+
+ [Serializable]
+ public abstract class ClientRequest : IMessage {
+ public string ClientId { get; set; }
+ public string Tuple { get; set; }
+ public int RequestNumber { get; set; }
+ public int ViewNumber { get; set; }
+
+ protected ClientRequest(string clientId) {
+ this.ClientId = clientId;
+ }
+
+ protected ClientRequest(string clientId, int requestNumber, string tuple) {
+ this.ClientId = clientId;
+ this.RequestNumber = requestNumber;
+ this.Tuple = tuple;
+ }
+
+ protected ClientRequest(int viewNumber, string clientId, int requestNumber, string tuple) {
+ this.ClientId = clientId;
+ this.RequestNumber = requestNumber;
+ this.Tuple = tuple;
+ this.ViewNumber = viewNumber;
+ }
+
+ public override string ToString() {
+ return $"{this.Tuple}";
+ }
+
+ public abstract IResponse Accept(IMessageSMRVisitor visitor);
+
+ public abstract IResponse Accept(IMessageXLVisitor visitor);
+ }
+
+ [Serializable]
+ public class ReadRequest : ClientRequest {
+ public ReadRequest(string clientId, int requestNumber, string tuple)
+ : base(clientId, requestNumber, tuple) { }
+
+ public ReadRequest(int viewNumber, string clientId, int requestNumber, string tuple)
+ : base(viewNumber, clientId, requestNumber, tuple) { }
+
+ public override string ToString() {
+ return $"{{ read {base.ToString()}, {this.ClientId}, {this.RequestNumber} }}";
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitReadRequest(this);
+ }
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitReadRequest(this);
+ }
+ }
+
+ [Serializable]
+ public class AddRequest : ClientRequest {
+ public AddRequest(string clientId, int requestNumber, string tuple)
+ : base(clientId, requestNumber, tuple) { }
+
+ public AddRequest(int viewNumber, string clientId, int requestNumber, string tuple)
+ : base(viewNumber, clientId, requestNumber, tuple) { }
+
+ public override string ToString() {
+ return $"{{ add {base.ToString()}, {this.ClientId}, {this.RequestNumber} }}";
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitAddRequest(this);
+ }
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitAddRequest(this);
+ }
+ }
+
+ [Serializable]
+ public class TakeRequest : ClientRequest {
+ public int RequestNumberLock { get; set; }
+
+ public TakeRequest(string clientId, int requestNumber, string tuple)
+ : base(clientId, requestNumber, tuple) { }
+
+
+ public TakeRequest(int viewNumber, string clientId, int requestNumber, string tuple)
+ : base(viewNumber, clientId, requestNumber, tuple) { }
+
+ public override string ToString() {
+ return $"{{ take: {{tuple: {base.ToString()}, ClientId: {this.ClientId}," +
+ $"RequestNumber: {this.RequestNumber}, RequestUnlockNumber: {this.RequestNumberLock} }} }}";
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitTakeRequest(this);
+ }
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitTakeRequest(this);
+ }
+ }
+
+ [Serializable]
+ public class ClientResponse : IResponse {
+ public int RequestNumber { get; set; }
+ public int ViewNumber { get; set; }
+ public string Result { get; set; }
+
+ public ClientResponse() { }
+
+ public ClientResponse(int requestNumber, int viewNumber) {
+ this.RequestNumber = requestNumber;
+ this.ViewNumber = viewNumber;
+ }
+
+ public ClientResponse(int requestNumber, int viewNumber, string result) {
+ this.RequestNumber = requestNumber;
+ this.ViewNumber = viewNumber;
+ this.Result = result;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Serializable/HandShake.cs b/tuple-space/MessageService/Serializable/HandShake.cs
new file mode 100644
index 0000000..72a1535
--- /dev/null
+++ b/tuple-space/MessageService/Serializable/HandShake.cs
@@ -0,0 +1,104 @@
+using System;
+using MessageService.Visitor;
+
+namespace MessageService.Serializable {
+
+ public enum Protocol {
+ StateMachineReplication,
+ XuLiskov
+ }
+
+ [Serializable]
+ public class ClientHandShakeRequest : ClientRequest {
+ public ClientHandShakeRequest(string clientId) : base(clientId) { }
+
+ public override string ToString() {
+ return $"Handshake {{ Client ID: {this.ClientId} }}";
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitClientHandShakeRequest(this);
+ }
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitClientHandShakeRequest(this);
+ }
+ }
+
+ [Serializable]
+ public class ClientHandShakeResponse : IResponse {
+ public Protocol ProtocolUsed { get; set; }
+ public int ViewNumber { get; set; }
+ public Uri[] ViewConfiguration { get; set; }
+ public Uri Leader { get; set; }
+
+ public ClientHandShakeResponse(Protocol protocolUsed, int viewNumber, Uri[] viewConfiguration) {
+ this.ProtocolUsed = protocolUsed;
+ this.ViewNumber = viewNumber;
+ this.ViewConfiguration = viewConfiguration;
+ }
+
+ public ClientHandShakeResponse(Protocol protocolUsed, int viewNumber, Uri[] viewConfiguration, Uri leader) {
+ this.ProtocolUsed = protocolUsed;
+ this.ViewNumber = viewNumber;
+ this.ViewConfiguration = viewConfiguration;
+ this.Leader = leader;
+ }
+
+ public override string ToString() {
+ return $"{{ ProtocolUsed: {this.ProtocolUsed}, View Number: {this.ViewNumber}," +
+ $" View Configuration: {this.ViewConfiguration}}}";
+ }
+ }
+
+ [Serializable]
+ public class ServerHandShakeRequest : IMessage {
+ public string ServerId { get; set; }
+ public Protocol ProtocolUsed { get; set; }
+
+ public ServerHandShakeRequest(string serverId, Protocol protocolUsed) {
+ this.ServerId = serverId;
+ this.ProtocolUsed = protocolUsed;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitServerHandShakeRequest(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitServerHandShakeRequest(this);
+ }
+ }
+
+ [Serializable]
+ public class ServerHandShakeResponse : IResponse {
+ public Uri[] ViewConfiguration { get; set; }
+
+ public ServerHandShakeResponse(Uri[] viewConfiguration) {
+ this.ViewConfiguration = viewConfiguration;
+ }
+ }
+
+ [Serializable]
+ public class JoinView : IMessage {
+ public string ServerId { get; set; }
+ public Uri Url { get; set; }
+
+ public JoinView(string serverId, Uri url) {
+ this.ServerId = serverId;
+ this.Url = url;
+ }
+
+ public IResponse Accept(IMessageVisitor visitor) {
+ return visitor.VisitJoinView(this);
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitJoinView(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitJoinView(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Serializable/HeartBeat.cs b/tuple-space/MessageService/Serializable/HeartBeat.cs
new file mode 100644
index 0000000..c43f6f7
--- /dev/null
+++ b/tuple-space/MessageService/Serializable/HeartBeat.cs
@@ -0,0 +1,31 @@
+using System;
+using MessageService.Visitor;
+
+namespace MessageService.Serializable {
+
+ [Serializable]
+ public class HeartBeat : IMessage {
+ public string ServerId { get; set; }
+
+ public HeartBeat(string serverId) {
+ this.ServerId = serverId;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitHeartBeat(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitHeartBeat(this);
+ }
+ }
+
+ [Serializable]
+ public class HeartBeatResponse : IResponse {
+ public int ViewNumber { get; set; }
+
+ public HeartBeatResponse(int viewNumber) {
+ this.ViewNumber = viewNumber;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Serializable/ProtocolMessages.cs b/tuple-space/MessageService/Serializable/ProtocolMessages.cs
new file mode 100644
index 0000000..d4f979a
--- /dev/null
+++ b/tuple-space/MessageService/Serializable/ProtocolMessages.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections.Generic;
+
+using MessageService.Visitor;
+using TupleSpace;
+
+namespace MessageService.Serializable {
+
+ // SMR ----------------------------------------------------------------------------------------------------------------
+ [Serializable]
+ public class PrepareMessage : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public ClientRequest ClientRequest { get; set; }
+ public int OpNumber { get; set; }
+ public int CommitNumber { get; set; }
+
+ public PrepareMessage(string serverId, int viewNumber, ClientRequest clientRequest, int opNumber, int commitNumber) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.ClientRequest = clientRequest;
+ this.OpNumber = opNumber;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitPrepareMessage(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+
+ public override string ToString() {
+ return $"{{ Server ID: {this.ServerId}, View Number: {this.ViewNumber}, Client Request: {this.ClientRequest}, "
+ + $"Op Number: {this.OpNumber}, Commit Number: {this.CommitNumber} }}";
+ }
+ }
+
+ [Serializable]
+ public class PrepareOk : IResponse {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int OpNumber { get; set; }
+
+ public PrepareOk(string serverId, int viewNumber, int opNumber) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.OpNumber = opNumber;
+ }
+
+ public override string ToString() {
+ return $"{{ Server ID: {this.ServerId}, View Number: {this.ViewNumber}, Op Number: {this.OpNumber} }}";
+ }
+ }
+
+ [Serializable]
+ public class CommitMessage : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int CommitNumber { get; set; }
+
+ public CommitMessage(string serverId, int viewNumber, int commitNumber) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitCommitMessage(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+
+ public override string ToString() {
+ return $"{{ Server ID: {this.ServerId}, View Number: {this.ViewNumber}, Commit Number: {this.CommitNumber} }}";
+ }
+ }
+
+ [Serializable]
+ public class StartViewChange : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+
+ public StartViewChange(string serverId, int viewNumber, SortedDictionary configuration) {
+ ServerId = serverId;
+ ViewNumber = viewNumber;
+ Configuration = configuration;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitStartViewChange(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Serializable]
+ public class StartViewChangeOk : IResponse {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+
+ public StartViewChangeOk(string serverId, int viewNumber, SortedDictionary configuration) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.Configuration = configuration;
+ }
+ }
+
+ [Serializable]
+ public class DoViewChange : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int OldViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+ public List Logger { get; set; }
+ public int OpNumber { get; set; }
+ public int CommitNumber { get; set; }
+
+ public DoViewChange(string serverId,
+ int viewNumber,
+ int oldViewNumber,
+ SortedDictionary configuration,
+ List logger,
+ int opNumber,
+ int commitNumber) {
+
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.OldViewNumber = oldViewNumber;
+ this.Configuration = configuration;
+ this.Logger = logger;
+ this.OpNumber = opNumber;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitDoViewChange(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Serializable]
+ public class StartChange : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+ public List Logger { get; set; }
+ public int OpNumber { get; set; }
+ public int CommitNumber { get; set; }
+
+ public StartChange(
+ string serverId,
+ int viewNumber,
+ SortedDictionary configuration,
+ List logger,
+ int opNumber,
+ int commitNumber) {
+
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.Configuration = configuration;
+ this.Logger = logger;
+ this.OpNumber = opNumber;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitStartChange(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Serializable]
+ public class Recovery : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int OpNumber { get; set; }
+ public int CommitNumber { get; set; }
+
+ public Recovery(string serverId, int viewNumber, int opNumber, int commitNumber) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.OpNumber = opNumber;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ return visitor.VisitRecovery(this);
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Serializable]
+ public class RecoveryResponse : IResponse {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int OpNumber { get; set; }
+ public int CommitNumber { get; set; }
+ public List SuffixLogger { get; set; }
+
+ public RecoveryResponse(string serverId) {
+ this.ServerId = serverId;
+ this.ViewNumber = -1;
+ this.OpNumber = -1;
+ this.CommitNumber = -1;
+ this.SuffixLogger = null;
+ }
+
+ public RecoveryResponse(string serverId, int viewNumber, int opNumber, int commitNumber, List suffixLogger) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.OpNumber = opNumber;
+ this.CommitNumber = commitNumber;
+ this.SuffixLogger = suffixLogger;
+ }
+ }
+
+ // XL ----------------------------------------------------------------------------------------------------------------
+
+ [Serializable]
+ public class GetAndLockRequest : ClientRequest {
+ public GetAndLockRequest(int viewNumber, string clientId, int requestNumber, string tuple) :
+ base(viewNumber, clientId, requestNumber, tuple) { }
+
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitGetAndLock(this);
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ throw new NotImplementedException();
+ }
+
+ public override string ToString() {
+ return $"{{ {base.ToString()}, {this.ClientId}, {this.RequestNumber}, {this.Tuple} }}";
+ }
+ }
+
+ [Serializable]
+ public class GetAndLockResponse : ClientResponse {
+ public string ServerId { get; set; }
+ public List Tuples { get; set; }
+
+ public GetAndLockResponse(string serverId, int requestNumber, int viewNumber, List tuples) :
+ base(requestNumber, viewNumber) {
+ this.Tuples = tuples;
+ this.ServerId = serverId;
+ }
+
+ public override string ToString() {
+ return $"{{ RequestNumber: {this.RequestNumber}, ViewNumber: {this.ViewNumber}, Tuples: {this.Tuples}}}";
+ }
+ }
+
+ [Serializable]
+ public class UnlockRequest : ClientRequest {
+
+ public UnlockRequest(int viewNumber, string clientId, int requestNumber)
+ : base(viewNumber, clientId, requestNumber, string.Empty) { }
+
+ public override IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitUnlockRequest(this);
+ }
+
+ public override IResponse Accept(IMessageSMRVisitor visitor) {
+ throw new NotImplementedException();
+ }
+
+ public override string ToString() {
+ return $"{{Client ID: {this.ClientId}, RequestNumber: {this.RequestNumber}}}";
+ }
+ }
+
+ [Serializable]
+ public class StartViewChangeXL : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+
+ public StartViewChangeXL(string serverId, int viewNumber, SortedDictionary configuration) {
+ ServerId = serverId;
+ ViewNumber = viewNumber;
+ Configuration = configuration;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ throw new NotSupportedException();
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitStartViewChangeXL(this);
+ }
+ }
+
+ [Serializable]
+ public class StartViewChangeXLOk : IResponse {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+
+ public StartViewChangeXLOk(string serverId, int viewNumber, SortedDictionary configuration) {
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.Configuration = configuration;
+ }
+ }
+
+ [Serializable]
+ public class DoViewChangeXL : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public int OldViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+ public TupleSpace.TupleSpace TupleSpace { get; set; }
+ public Dictionary> ClientTable { get; set; }
+ public int CommitNumber { get; set; }
+
+ public DoViewChangeXL(
+ string serverId,
+ int viewNumber,
+ int oldViewNumber,
+ SortedDictionary configuration,
+ TupleSpace.TupleSpace tupleSpace,
+ Dictionary> clientTable,
+ int commitNumber) {
+
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.OldViewNumber = oldViewNumber;
+ this.Configuration = configuration;
+ this.TupleSpace = tupleSpace;
+ this.ClientTable = clientTable;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ throw new NotSupportedException();
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitDoViewChangeXL(this);
+ }
+ }
+
+ [Serializable]
+ public class StartChangeXL : IMessage {
+ public string ServerId { get; set; }
+ public int ViewNumber { get; set; }
+ public SortedDictionary Configuration { get; set; }
+ public TupleSpace.TupleSpace TupleSpace { get; set; }
+ public Dictionary> ClientTable { get; set; }
+ public int CommitNumber { get; set; }
+
+ public StartChangeXL(
+ string serverId,
+ int viewNumber,
+ SortedDictionary configuration,
+ TupleSpace.TupleSpace tupleSpace,
+ Dictionary> clientTable,
+ int commitNumber) {
+
+ this.ServerId = serverId;
+ this.ViewNumber = viewNumber;
+ this.Configuration = configuration;
+ this.TupleSpace = tupleSpace;
+ this.ClientTable = clientTable;
+ this.CommitNumber = commitNumber;
+ }
+
+ public IResponse Accept(IMessageSMRVisitor visitor) {
+ throw new NotSupportedException();
+ }
+
+ public IResponse Accept(IMessageXLVisitor visitor) {
+ return visitor.VisitStartChangeXL(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/ServerMessageWrapper.cs b/tuple-space/MessageService/ServerMessageWrapper.cs
new file mode 100644
index 0000000..c3a7b73
--- /dev/null
+++ b/tuple-space/MessageService/ServerMessageWrapper.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Runtime.Remoting.Channels;
+using System.Runtime.Remoting.Channels.Tcp;
+
+using log4net;
+
+namespace MessageService {
+ public class ServerMessageWrapper {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(ServerMessageWrapper));
+
+ private readonly TcpChannel channel;
+
+ public MessageServiceServer ServiceServer { get; }
+
+ public MessageServiceClient ServiceClient { get; }
+
+ private readonly Uri url;
+ private readonly int minDelay;
+ private readonly int maxDelay;
+
+ private bool frozen;
+
+ public ServerMessageWrapper(Uri myUrl, IProtocol protocol, int minDelay, int maxDelay) {
+ this.url = myUrl;
+ this.minDelay = minDelay;
+ this.maxDelay = maxDelay;
+ this.frozen = false;
+
+ // create tcp channel
+ this.channel = new TcpChannel(myUrl.Port);
+ ChannelServices.RegisterChannel(this.channel, false);
+ Log.Info("TCP channel created.");
+
+ // create MessageServiceServer
+ this.ServiceServer = new MessageServiceServer(protocol, minDelay, maxDelay);
+
+ // create MessageServiceClient
+ this.ServiceClient = new MessageServiceClient(this.channel);
+ }
+
+ public string Status() {
+ string status =
+ $"Host: {url.Host} {Environment.NewLine}" +
+ $"Port: {url.Port} {Environment.NewLine}" +
+ $"Frozen: {frozen} {Environment.NewLine}" +
+ $"MinDelay: {minDelay} {Environment.NewLine}" +
+ $"MaxDelay: {maxDelay} {Environment.NewLine}";
+ return status;
+ }
+
+ public void Freeze() {
+ if (!frozen) {
+ this.ServiceClient.Freeze();
+ this.ServiceServer.Freeze();
+ this.frozen = true;
+ }
+ }
+
+ public void Unfreeze() {
+ if (frozen) {
+ this.ServiceClient.Unfreeze();
+ this.ServiceServer.Unfreeze();
+ this.frozen = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Timeout.cs b/tuple-space/MessageService/Timeout.cs
new file mode 100644
index 0000000..f284b07
--- /dev/null
+++ b/tuple-space/MessageService/Timeout.cs
@@ -0,0 +1,24 @@
+namespace MessageService {
+ public static class Timeout {
+
+ public const int TIMEOUT_SERVER_HANDSHAKE = 10000;
+
+ public const int TIMEOUT_HEART_BEAT = 20000;
+
+ public const int TIMEOUT_HEART_BEAT_XL = 10000;
+
+ public const int TIMEOUT_INIT = 11000;
+
+ public const int TIMEOUT_RECOVERY = 5000;
+
+ public const int TIMEOUT_VIEW_CHANGE = 5000;
+
+ public const int TIMEOUT_SMR_CLIENT = 5000;
+
+ public const int TIMEOUT_SMR_CLIENT_WAIT = 1000;
+
+ public const int TIMEOUT_XL_CLIENT = 15000;
+
+ public const int TIMEOUT_XL_CLIENT_WAIT = 1000;
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Utils/ConfigurationUtils.cs b/tuple-space/MessageService/Utils/ConfigurationUtils.cs
new file mode 100644
index 0000000..d153a71
--- /dev/null
+++ b/tuple-space/MessageService/Utils/ConfigurationUtils.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StateMachineReplication.Utils {
+ public static class ConfigurationUtils {
+ public static bool CompareConfigurations(SortedDictionary conf1,
+ SortedDictionary conf2) {
+ if (conf1 == null || conf2 == null) {
+ return false;
+ }
+ return conf1.Count == conf2.Count &&
+ conf1.Keys.All(key => conf2.ContainsKey(key) && Uri.Equals(conf1[key], conf2[key]));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Visitor/IMessageVisitor.cs b/tuple-space/MessageService/Visitor/IMessageVisitor.cs
new file mode 100644
index 0000000..6844772
--- /dev/null
+++ b/tuple-space/MessageService/Visitor/IMessageVisitor.cs
@@ -0,0 +1,45 @@
+using MessageService.Serializable;
+
+namespace MessageService.Visitor {
+ public interface IMessageVisitor {
+ IResponse VisitAddRequest(AddRequest addRequest);
+
+ IResponse VisitTakeRequest(TakeRequest takeRequest);
+
+ IResponse VisitReadRequest(ReadRequest readRequest);
+
+ IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest);
+
+ IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest);
+
+ IResponse VisitJoinView(JoinView joinView);
+
+ IResponse VisitHeartBeat(HeartBeat heartBeat);
+ }
+
+ public interface IMessageSMRVisitor : IMessageVisitor {
+ IResponse VisitPrepareMessage(PrepareMessage prepareMessage);
+
+ IResponse VisitCommitMessage(CommitMessage commitMessage);
+
+ IResponse VisitStartViewChange(StartViewChange startViewChange);
+
+ IResponse VisitDoViewChange(DoViewChange doViewChange);
+
+ IResponse VisitStartChange(StartChange startChange);
+
+ IResponse VisitRecovery(Recovery recovery);
+ }
+
+ public interface IMessageXLVisitor : IMessageVisitor {
+ IResponse VisitGetAndLock(GetAndLockRequest getAndLockRequest);
+
+ IResponse VisitUnlockRequest(UnlockRequest unlockRequest);
+
+ IResponse VisitStartViewChangeXL(StartViewChangeXL startViewChange);
+
+ IResponse VisitDoViewChangeXL(DoViewChangeXL doViewChange);
+
+ IResponse VisitStartChangeXL(StartChangeXL startChange);
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/Wrappers.cs b/tuple-space/MessageService/Wrappers.cs
new file mode 100644
index 0000000..9b2150f
--- /dev/null
+++ b/tuple-space/MessageService/Wrappers.cs
@@ -0,0 +1,27 @@
+using System.Collections.Concurrent;
+
+namespace MessageService {
+ public class Responses : IResponses {
+ private readonly ConcurrentBag responses;
+
+ public Responses() {
+ this.responses = new ConcurrentBag();
+ }
+
+ public void Add(IResponse response) {
+ this.responses.Add(response);
+ }
+
+ public IResponse[] ToArray() {
+ return this.responses.ToArray();
+ }
+
+ public int Count() {
+ return this.responses.Count;
+ }
+
+ public override string ToString() {
+ return responses.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/MessageService/packages.config b/tuple-space/MessageService/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/MessageService/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/ProcessCreationService/App.config b/tuple-space/ProcessCreationService/App.config
new file mode 100644
index 0000000..731f6de
--- /dev/null
+++ b/tuple-space/ProcessCreationService/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/ProcessCreationService/ProcessCreationService.cs b/tuple-space/ProcessCreationService/ProcessCreationService.cs
new file mode 100644
index 0000000..15b6ac3
--- /dev/null
+++ b/tuple-space/ProcessCreationService/ProcessCreationService.cs
@@ -0,0 +1,53 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using PuppetMasterService;
+
+namespace ProcessCreationService {
+
+ public class ProcessCreationService : MarshalByRefObject, IProcessCreationService {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ProcessCreationService));
+
+ private const string BASE_DIR = "..\\..\\..\\";
+ private const string SERVER_EXE = BASE_DIR + "Server\\bin\\Debug\\Server.exe";
+ private const string CLIENT_EXE = BASE_DIR + "Client\\bin\\Debug\\Client.exe";
+
+ public void CreateClient(string clientId, Uri url, string scriptName) {
+ Log.Info($"Trying to initialize client: ClientId = {clientId}, url = {url}, scriptName = {scriptName}");
+ try {
+ Process process = new Process {
+ StartInfo = {
+ FileName = CLIENT_EXE,
+ Arguments = $"{clientId} {url} {scriptName}"
+ }
+ };
+
+ process.Start();
+ } catch (Win32Exception e) {
+ Log.Error($"{e.Message} Could not initialize client: ClientId = {clientId}, url = {url}, scriptName = {scriptName}");
+ }
+ }
+
+ public void CreateServer(string serverId, Uri url, int minDelay, int maxDelay, string protocol) {
+ Log.Info($"Trying to initialize server: ServerId = {serverId}, url = {url}, " +
+ $"minDelay = {minDelay}, maxDelay = {maxDelay}, protocol = {protocol}");
+ try {
+ Process process = new Process {
+ StartInfo = {
+ FileName = SERVER_EXE,
+ Arguments = $"{serverId} {url} {minDelay} {maxDelay} {protocol}"
+ }
+ };
+
+ process.Start();
+ } catch (Win32Exception e) {
+ Log.Error($"{e.Message} Could not initialize client: ServerId = {serverId}, url = {url}, " +
+ $"minDelay = {minDelay}, maxDelay = {maxDelay}, protocol = {protocol}");
+ }
+ }
+
+ public override object InitializeLifetimeService() {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/ProcessCreationService/ProcessCreationService.csproj b/tuple-space/ProcessCreationService/ProcessCreationService.csproj
new file mode 100644
index 0000000..dcc49a0
--- /dev/null
+++ b/tuple-space/ProcessCreationService/ProcessCreationService.csproj
@@ -0,0 +1,69 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {055B7EB5-D8F9-4B15-B206-633226998322}
+ Exe
+ ProcessCreationService
+ ProcessCreationService
+ v4.6.1
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {829ac545-bc95-43e8-82e6-8001c5c26c1f}
+ PuppetMasterService
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/ProcessCreationService/Program.cs b/tuple-space/ProcessCreationService/Program.cs
new file mode 100644
index 0000000..ee91980
--- /dev/null
+++ b/tuple-space/ProcessCreationService/Program.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Runtime.Remoting;
+using System.Runtime.Remoting.Channels;
+using System.Runtime.Remoting.Channels.Tcp;
+
+namespace ProcessCreationService {
+ class Program {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Program));
+
+ static void Main(string[] args) {
+ // Create and register channel
+ TcpChannel channel = new TcpChannel(PuppetMasterService.Constants.PROCESS_CREATION_SERVICE_PORT);
+ ChannelServices.RegisterChannel(channel, false);
+ Log.Info($"TCP channel created at port {PuppetMasterService.Constants.PROCESS_CREATION_SERVICE_PORT}");
+
+ // Register service
+ RemotingConfiguration.RegisterWellKnownServiceType(
+ typeof(ProcessCreationService),
+ PuppetMasterService.Constants.PROCESS_CREATION_SERVICE,
+ WellKnownObjectMode.Singleton);
+
+ Console.WriteLine("Press enter to exit...");
+ Console.ReadLine();
+ }
+ }
+}
diff --git a/tuple-space/ProcessCreationService/Properties/AssemblyInfo.cs b/tuple-space/ProcessCreationService/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4eab722
--- /dev/null
+++ b/tuple-space/ProcessCreationService/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ProcessCreationService")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ProcessCreationService")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("055b7eb5-d8f9-4b15-b206-633226998322")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/ProcessCreationService/log4net.config.xml b/tuple-space/ProcessCreationService/log4net.config.xml
new file mode 100644
index 0000000..79f530d
--- /dev/null
+++ b/tuple-space/ProcessCreationService/log4net.config.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/ProcessCreationService/packages.config b/tuple-space/ProcessCreationService/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/ProcessCreationService/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/BasicNode.cs b/tuple-space/PuppetMaster/CommandStructure/BasicNode.cs
new file mode 100644
index 0000000..d3f6580
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/BasicNode.cs
@@ -0,0 +1,9 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public abstract class BasicNode {
+ public BasicNode() {}
+
+ public abstract void Accept(IBasicVisitor visitor);
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/Command.cs b/tuple-space/PuppetMaster/CommandStructure/Command.cs
new file mode 100644
index 0000000..a965374
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Command.cs
@@ -0,0 +1,7 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public abstract class Command : BasicNode {
+ public Command() {}
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/Crash.cs b/tuple-space/PuppetMaster/CommandStructure/Crash.cs
new file mode 100644
index 0000000..8deca84
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Crash.cs
@@ -0,0 +1,11 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Crash : DebuggingCommand {
+ public Crash(string processName) : base(processName) { }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitCrash(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/CreateClient.cs b/tuple-space/PuppetMaster/CommandStructure/CreateClient.cs
new file mode 100644
index 0000000..97aeeca
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/CreateClient.cs
@@ -0,0 +1,20 @@
+using System;
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class CreateClient : Command {
+ public string Id { get; }
+ public Uri Url { get; }
+ public string ScriptFile { get; }
+
+ public CreateClient(string id, Uri url, string scriptFile) {
+ this.Id = id;
+ this.Url = new Uri($"tcp://{url.Host}:{url.Port}");
+ this.ScriptFile = scriptFile;
+ }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitCreateClient(this);
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/CreateServer.cs b/tuple-space/PuppetMaster/CommandStructure/CreateServer.cs
new file mode 100644
index 0000000..3abe0cd
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/CreateServer.cs
@@ -0,0 +1,24 @@
+using System;
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class CreateServer : Command {
+ public string Id { get; }
+ public Uri Url { get; }
+ public int MinDelay { get; }
+ public int MaxDelay { get; }
+ public string Protocol { get; }
+
+ public CreateServer(string id, Uri url, int minDelay, int maxDelay, string protocol) {
+ this.Id = id;
+ this.Url = new Uri($"tcp://{url.Host}:{url.Port}");
+ this.MinDelay = minDelay;
+ this.MaxDelay = maxDelay;
+ this.Protocol = protocol;
+ }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitCreateServer(this);
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/DebuggingCommand.cs b/tuple-space/PuppetMaster/CommandStructure/DebuggingCommand.cs
new file mode 100644
index 0000000..8d2e87c
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/DebuggingCommand.cs
@@ -0,0 +1,9 @@
+namespace PuppetMaster.CommandStructure {
+ public abstract class DebuggingCommand : Command {
+ public DebuggingCommand(string processName) {
+ this.ProcessName = processName;
+ }
+
+ public string ProcessName { get; }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/Exit.cs b/tuple-space/PuppetMaster/CommandStructure/Exit.cs
new file mode 100644
index 0000000..20c2b0a
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Exit.cs
@@ -0,0 +1,13 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Exit : Command {
+
+ public Exit() {}
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitExit(this);
+ }
+
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/Freeze.cs b/tuple-space/PuppetMaster/CommandStructure/Freeze.cs
new file mode 100644
index 0000000..58fc83e
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Freeze.cs
@@ -0,0 +1,11 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Freeze : DebuggingCommand {
+ public Freeze(string processName) : base(processName) { }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitFreeze(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/Script.cs b/tuple-space/PuppetMaster/CommandStructure/Script.cs
new file mode 100644
index 0000000..74a3b8d
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Script.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Script : BasicNode {
+ private readonly List nodes;
+
+ public Script() {
+ this.nodes = new List();
+ }
+
+ public List Nodes => this.nodes;
+
+ public void AddNode(Command node) {
+ this.nodes.Add(node);
+ }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitScript(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/Status.cs b/tuple-space/PuppetMaster/CommandStructure/Status.cs
new file mode 100644
index 0000000..767c51a
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Status.cs
@@ -0,0 +1,13 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Status : Command {
+
+ public Status() {}
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitStatus(this);
+ }
+
+ }
+}
diff --git a/tuple-space/PuppetMaster/CommandStructure/Unfreeze.cs b/tuple-space/PuppetMaster/CommandStructure/Unfreeze.cs
new file mode 100644
index 0000000..79777fc
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Unfreeze.cs
@@ -0,0 +1,11 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Unfreeze : DebuggingCommand {
+ public Unfreeze(string processName) : base(processName) { }
+
+ public override void Accept(IBasicVisitor visitor) {
+ visitor.VisitUnfreeze(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/CommandStructure/Wait.cs b/tuple-space/PuppetMaster/CommandStructure/Wait.cs
new file mode 100644
index 0000000..19b05e0
--- /dev/null
+++ b/tuple-space/PuppetMaster/CommandStructure/Wait.cs
@@ -0,0 +1,18 @@
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster.CommandStructure {
+ public class Wait : Command {
+ private readonly int time;
+
+ public Wait(int time) {
+ this.time = time;
+ }
+
+ public override void Accept(IBasicVisitor visitor)
+ {
+ visitor.VisitWait(this);
+ }
+
+ public int Time => this.time;
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/Exceptions/IncorrectCommandException.cs b/tuple-space/PuppetMaster/Exceptions/IncorrectCommandException.cs
new file mode 100644
index 0000000..69a1ae0
--- /dev/null
+++ b/tuple-space/PuppetMaster/Exceptions/IncorrectCommandException.cs
@@ -0,0 +1,10 @@
+using System;
+namespace PuppetMaster.Exceptions {
+ public class IncorrectCommandException : Exception {
+ public IncorrectCommandException(int index, string message) : base($"Incorrect command in line {index+1}: {message}") {
+ }
+
+ public IncorrectCommandException(int index) : base($"Incorrect command in line {index + 1}") {
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/Parser.cs b/tuple-space/PuppetMaster/Parser.cs
new file mode 100644
index 0000000..d1cd76f
--- /dev/null
+++ b/tuple-space/PuppetMaster/Parser.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Linq;
+using System.Text.RegularExpressions;
+using PuppetMaster.CommandStructure;
+using PuppetMaster.Exceptions;
+
+namespace PuppetMaster {
+ public static class Parser {
+ public static Script Parse(string command) {
+ return Parse(new[] { command });
+ }
+
+ public static Script Parse(string[] lines) {
+ Script script = new Script();
+
+ for (int i = 0; i < lines.Length; i++) {
+
+ if (lines[i].StartsWith("\n") || lines[i].StartsWith("\r\n") || string.IsNullOrWhiteSpace(lines[i])) {
+ continue;
+ }
+ if (lines[i].StartsWith(" ") || lines[i].StartsWith("\t")) {
+ throw new IncorrectCommandException(i);
+ }
+
+ Regex exprRegex = new Regex("(\\n|\\r)+");
+ string command = exprRegex.Replace(lines[i], string.Empty);
+
+ string[] words = Regex.Split(command, "(\\s|\\t)+")
+ .Where(s => !string.IsNullOrWhiteSpace(s))
+ .ToArray();
+
+ string expectedMessage = "Unexpected Command";
+ try {
+ switch (words[0]) {
+ case "Server": {
+ expectedMessage =
+ $"{Environment.NewLine}Expected: Server server_id URL min_delay max_delay protocol";
+
+ if (words.Length != 6) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ string id = words[1];
+ Uri url = new Uri(words[2]);
+ int minDelay = int.Parse(words[3]);
+ int maxDelay = int.Parse(words[4]);
+ string protocol = words[5];
+
+ if (minDelay < 0 || maxDelay < 0 || minDelay > maxDelay) {
+ throw new IncorrectCommandException(i,
+ $"{Environment.NewLine}Expected: min_delay >= 0 and max_delay >= 0 and min_delay <= max_delay");
+ }
+
+ script.AddNode(new CreateServer(id, url, minDelay, maxDelay, protocol));
+ break;
+ }
+ case "Client": {
+ expectedMessage = $"{Environment.NewLine}Expected: Client client_id URL script_file";
+
+ if (words.Length != 4) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ string id = words[1];
+ Uri url = new Uri(words[2]);
+ string scriptFile = words[3];
+ script.AddNode(new CreateClient(id, url, scriptFile));
+ break;
+ }
+ case "Status": {
+ expectedMessage = $"{Environment.NewLine}Expected: Status";
+
+ if (words.Length != 1) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ script.AddNode(new Status());
+ break;
+ }
+ case "Crash": {
+ expectedMessage = $"{Environment.NewLine}Expected: Crash server_id";
+
+ if (words.Length != 2) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ string processName = words[1];
+ script.AddNode(new Crash(processName));
+ break;
+ }
+ case "Freeze": {
+ expectedMessage = $"{Environment.NewLine}Expected: Freeze server_id";
+
+ if (words.Length != 2) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ string processName = words[1];
+ script.AddNode(new Freeze(processName));
+ break;
+ }
+ case "Unfreeze": {
+ expectedMessage = $"{Environment.NewLine}Expected: Unfreeze server_id";
+
+ if (words.Length != 2) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ string processName = words[1];
+ script.AddNode(new Unfreeze(processName));
+ break;
+ }
+ case "Wait": {
+ expectedMessage = $"{Environment.NewLine}Expected: Wait x_ms";
+
+ if (words.Length != 2) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ int time = int.Parse(words[1]);
+ script.AddNode(new Wait(time));
+ break;
+ }
+ case "Exit": {
+ expectedMessage = $"{Environment.NewLine}Expected: Exit";
+
+ if (words.Length != 1) {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+
+ script.AddNode(new Exit());
+ break;
+ }
+ default:
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+ } catch (Exception e) {
+ if (e is IncorrectCommandException) {
+ throw;
+ } else {
+ throw new IncorrectCommandException(i, expectedMessage);
+ }
+ }
+ }
+ return script;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/Program.cs b/tuple-space/PuppetMaster/Program.cs
new file mode 100644
index 0000000..926da2c
--- /dev/null
+++ b/tuple-space/PuppetMaster/Program.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Runtime.Remoting.Channels;
+using System.Runtime.Remoting.Channels.Tcp;
+using PuppetMaster.CommandStructure;
+using PuppetMaster.Exceptions;
+using PuppetMaster.Visitor;
+
+namespace PuppetMaster {
+ public static class PuppetMaster {
+ public static void Main(string[] args) {
+ // Create and register channel
+ TcpChannel channel = new TcpChannel(PuppetMasterService.Constants.PUPPET_MASTER_PORT);
+ ChannelServices.RegisterChannel(channel, false);
+
+ IBasicVisitor interpreter = new Interpreter();
+ try {
+ if (args.Length == 1) {
+ Console.WriteLine("Reading file...");
+ Script script = Parser.Parse(System.IO.File.ReadAllLines(args[0]));
+ script.Accept(interpreter);
+ }
+ } catch (Exception ex) {
+ if (ex is IncorrectCommandException) {
+ Console.WriteLine(ex.Message);
+ } else {
+ throw;
+ }
+ }
+
+ while (true) {
+ try {
+ Console.Write(">>> ");
+ Parser.Parse(Console.ReadLine()).Accept(interpreter);
+ } catch (Exception ex) {
+ if (ex is IncorrectCommandException) {
+ Console.WriteLine(ex.Message);
+ } else {
+ throw;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/Properties/AssemblyInfo.cs b/tuple-space/PuppetMaster/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..fc77085
--- /dev/null
+++ b/tuple-space/PuppetMaster/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("PuppetMaster")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("${AuthorCopyright}")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/tuple-space/PuppetMaster/PuppetMaster.csproj b/tuple-space/PuppetMaster/PuppetMaster.csproj
new file mode 100644
index 0000000..fd7fd9e
--- /dev/null
+++ b/tuple-space/PuppetMaster/PuppetMaster.csproj
@@ -0,0 +1,65 @@
+
+
+
+ Debug
+ AnyCPU
+ {E00B43C7-21CD-4D78-B771-2A88AB9DE81D}
+ Exe
+ PuppetMaster
+ PuppetMaster
+ v4.6.1
+
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ true
+
+
+ true
+ bin\Release
+ prompt
+ 4
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {829ac545-bc95-43e8-82e6-8001c5c26c1f}
+ PuppetMasterService
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/PuppetMaster.csproj.user b/tuple-space/PuppetMaster/PuppetMaster.csproj.user
new file mode 100644
index 0000000..2df6942
--- /dev/null
+++ b/tuple-space/PuppetMaster/PuppetMaster.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ ../../sample-scripts/puppetMaster.txt
+
+
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/Visitor/IBasicVisitor.cs b/tuple-space/PuppetMaster/Visitor/IBasicVisitor.cs
new file mode 100644
index 0000000..ff01342
--- /dev/null
+++ b/tuple-space/PuppetMaster/Visitor/IBasicVisitor.cs
@@ -0,0 +1,23 @@
+using PuppetMaster.CommandStructure;
+
+namespace PuppetMaster.Visitor {
+ public interface IBasicVisitor {
+ void VisitCrash(Crash crash);
+
+ void VisitCreateClient(CreateClient createClient);
+
+ void VisitCreateServer(CreateServer createServer);
+
+ void VisitFreeze(Freeze freeze);
+
+ void VisitScript(Script script);
+
+ void VisitStatus(Status status);
+
+ void VisitUnfreeze(Unfreeze unfreeze);
+
+ void VisitWait(Wait wait);
+
+ void VisitExit(Exit exit);
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/Visitor/Interpreter.cs b/tuple-space/PuppetMaster/Visitor/Interpreter.cs
new file mode 100644
index 0000000..283792b
--- /dev/null
+++ b/tuple-space/PuppetMaster/Visitor/Interpreter.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using PuppetMaster.CommandStructure;
+using PuppetMasterService;
+
+namespace PuppetMaster.Visitor {
+ public class Interpreter : IBasicVisitor {
+ private readonly ConcurrentDictionary servers;
+ private readonly ConcurrentDictionary clients;
+
+ public Interpreter() {
+ this.servers = new ConcurrentDictionary();
+ this.clients = new ConcurrentDictionary();
+ }
+
+ public void VisitCrash(Crash crash) {
+ Task.Factory.StartNew(() => {
+ IPuppetMasterService server = this.GetServerService(crash.ProcessName);
+ try {
+ server.Crash();
+ } catch (SocketException) {
+ // It will receive socket exception if successful.
+ this.servers.TryRemove(crash.ProcessName, out _);
+ }
+ });
+ }
+
+ public void VisitCreateClient(CreateClient createClient) {
+ Task.Factory.StartNew(() => {
+ if (this.clients.ContainsKey(createClient.Id)) {
+ Console.WriteLine($"The client_id {createClient.Id} is already associated.");
+ return;
+ }
+
+ this.clients.TryAdd(createClient.Id, createClient.Url);
+ IProcessCreationService processCreationService = GetProcessCreationService(createClient.Url);
+
+ processCreationService.CreateClient(
+ createClient.Id,
+ createClient.Url,
+ createClient.ScriptFile);
+
+ });
+ }
+
+ public void VisitCreateServer(CreateServer createServer) {
+ Task.Factory.StartNew(() => {
+ if (this.servers.ContainsKey(createServer.Id)) {
+ Console.WriteLine($"The server_id {createServer.Id} is already associated.");
+ return;
+ }
+
+ this.servers.TryAdd(createServer.Id, createServer.Url);
+
+ IProcessCreationService processCreationService = GetProcessCreationService(createServer.Url);
+ processCreationService.CreateServer(
+ createServer.Id,
+ createServer.Url,
+ createServer.MinDelay,
+ createServer.MaxDelay,
+ createServer.Protocol);
+ });
+ }
+
+ public void VisitFreeze(Freeze freeze) {
+ Task.Factory.StartNew(() => {
+ IPuppetMasterService server = this.GetServerService(freeze.ProcessName);
+ server.Freeze();
+ });
+ }
+
+ public void VisitScript(Script script) {
+ foreach (Command node in script.Nodes) {
+ node.Accept(this);
+ }
+ }
+
+ public void VisitStatus(Status status) {
+ Task.Factory.StartNew(() => {
+ StringBuilder statusMessage = new StringBuilder();
+ foreach (KeyValuePair entry in this.servers) {
+ statusMessage.Append(
+ $"=============================================================================={Environment.NewLine}" +
+ $"================================= SERVER INFO ================================{Environment.NewLine}" +
+ $"=============================================================================={Environment.NewLine}" +
+ $"Server ID: {entry.Key} {Environment.NewLine}");
+
+ try {
+ IPuppetMasterService server = GetServerService(entry.Value);
+
+ statusMessage.Append(server.Status());
+ } catch (System.Net.Sockets.SocketException) {
+ statusMessage.Append($"Server is dead. {Environment.NewLine}");
+ }
+
+ statusMessage.Append(
+ $"=============================================================================={Environment.NewLine}");
+ }
+ Console.Write(statusMessage.ToString());
+ });
+ }
+
+ public void VisitUnfreeze(Unfreeze unfreeze) {
+ Task.Factory.StartNew(() => {
+ IPuppetMasterService server = this.GetServerService(unfreeze.ProcessName);
+ server.Unfreeze();
+ });
+ }
+
+ public void VisitWait(Wait wait) {
+ Thread.Sleep(wait.Time);
+ }
+
+ public void VisitExit(Exit exit) {
+ Environment.Exit(0);
+ }
+
+ private IProcessCreationService GetProcessCreationService(Uri url) {
+ return (IProcessCreationService)
+ Activator.GetObject(
+ typeof(IProcessCreationService),
+ $"tcp://{url.Host}:{Constants.PROCESS_CREATION_SERVICE_PORT}/{Constants.PROCESS_CREATION_SERVICE}");
+ }
+
+ private IPuppetMasterService GetServerService(string processName) {
+ this.servers.TryGetValue(processName, out Uri url);
+ return this.GetServerService(url);
+ }
+
+ private IPuppetMasterService GetServerService(Uri url) {
+ return (IPuppetMasterService)
+ Activator.GetObject(
+ typeof(IPuppetMasterService),
+ $"tcp://{url.Host}:{url.Port}/{Constants.PUPPET_MASTER_SERVICE}");
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/Visitor/Writer.cs b/tuple-space/PuppetMaster/Visitor/Writer.cs
new file mode 100644
index 0000000..1b05ce8
--- /dev/null
+++ b/tuple-space/PuppetMaster/Visitor/Writer.cs
@@ -0,0 +1,47 @@
+using System;
+using PuppetMaster.CommandStructure;
+
+namespace PuppetMaster.Visitor {
+ public class Writer : IBasicVisitor {
+ public Writer() {}
+
+ public void VisitCrash(Crash crash) {
+ Console.WriteLine($"Crash {crash.ProcessName}");
+ }
+
+ public void VisitCreateClient(CreateClient createClient) {
+ Console.WriteLine($"Client {createClient.Id} {createClient.Url} {createClient.ScriptFile}");
+ }
+
+ public void VisitCreateServer(CreateServer createServer) {
+ Console.WriteLine($"Server {createServer.Id} {createServer.Url} {createServer.MinDelay} {createServer.MaxDelay} {createServer.Protocol}");
+ }
+
+ public void VisitFreeze(Freeze freeze) {
+ Console.WriteLine($"Freeze {freeze.ProcessName}");
+ }
+
+ public void VisitScript(Script script) {
+ Console.WriteLine("Script:\n");
+ foreach (Command node in script.Nodes) {
+ node.Accept(this);
+ }
+ }
+
+ public void VisitStatus(Status status) {
+ Console.WriteLine("Status");
+ }
+
+ public void VisitUnfreeze(Unfreeze unfreeze) {
+ Console.WriteLine($"Unfreeze {unfreeze.ProcessName}");
+ }
+
+ public void VisitWait(Wait wait) {
+ Console.WriteLine($"Wait {wait.Time}");
+ }
+
+ public void VisitExit(Exit exit) {
+ Console.WriteLine($"Exit");
+ }
+ }
+}
diff --git a/tuple-space/PuppetMaster/app.config b/tuple-space/PuppetMaster/app.config
new file mode 100644
index 0000000..3dbff35
--- /dev/null
+++ b/tuple-space/PuppetMaster/app.config
@@ -0,0 +1,3 @@
+
+
+
diff --git a/tuple-space/PuppetMaster/sample-scripts/puppetMaster.txt b/tuple-space/PuppetMaster/sample-scripts/puppetMaster.txt
new file mode 100644
index 0000000..9176a6f
--- /dev/null
+++ b/tuple-space/PuppetMaster/sample-scripts/puppetMaster.txt
@@ -0,0 +1,6 @@
+Server s1 tcp://localhost:8080/ 10 1000 SMRA
+Wait 11000
+Server s2 tcp://localhost:8081/ 10 1000 SMRA
+Server s3 tcp://localhost:8082/ 10 1000 SMRA
+Client c1 tcp://localhost:8101 ../../../Client/sample-scripts/sampleClientScript2-1.txt
+Client c2 tcp://localhost:8102 ../../../Client/sample-scripts/sampleClientScript2-2.txt
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/sample-scripts/puppetMaster2.txt b/tuple-space/PuppetMaster/sample-scripts/puppetMaster2.txt
new file mode 100644
index 0000000..aa979cd
--- /dev/null
+++ b/tuple-space/PuppetMaster/sample-scripts/puppetMaster2.txt
@@ -0,0 +1,6 @@
+Server s1 tcp://localhost:8080/ 10 1000 SMR
+Wait 11000
+Server s2 tcp://localhost:8081/ 10 1000 SMR
+Server s3 tcp://localhost:8082/ 10 1000 SMR
+Client c1 tcp://localhost:8101 ../../../Client/sample-scripts/sampleClientScript2-1.txt
+Client c2 tcp://localhost:8102 ../../../Client/sample-scripts/sampleClientScript2-2.txt
\ No newline at end of file
diff --git a/tuple-space/PuppetMaster/sample-scripts/samplePMScript.txt b/tuple-space/PuppetMaster/sample-scripts/samplePMScript.txt
new file mode 100644
index 0000000..f7c65df
--- /dev/null
+++ b/tuple-space/PuppetMaster/sample-scripts/samplePMScript.txt
@@ -0,0 +1,7 @@
+Server s1 tcp://1.2.3.4:50001/S 10 1000
+Wait 500
+Client c1 tcp://4.5.6.7:60001/C AScriptName.txt
+Status
+Freeze s1
+Unfreeze s1
+Crash s1
diff --git a/tuple-space/PuppetMasterService/Interfaces.cs b/tuple-space/PuppetMasterService/Interfaces.cs
new file mode 100644
index 0000000..f49467c
--- /dev/null
+++ b/tuple-space/PuppetMasterService/Interfaces.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace PuppetMasterService {
+
+ public static class Constants {
+ public const int PROCESS_CREATION_SERVICE_PORT = 10000;
+ public const int PUPPET_MASTER_PORT = 10001;
+ public const string PROCESS_CREATION_SERVICE = "pcs";
+ public const string PUPPET_MASTER_SERVICE = "pms";
+ }
+
+ public interface IProcessCreationService {
+ void CreateServer(string serverId, Uri url, int minDelay, int maxDelay, string protocol);
+
+ void CreateClient(string clientId, Uri url, string scriptName);
+ }
+
+ public interface IPuppetMasterService {
+ string Status();
+
+ void Crash();
+
+ void Freeze();
+
+ void Unfreeze();
+ }
+}
diff --git a/tuple-space/PuppetMasterService/Properties/AssemblyInfo.cs b/tuple-space/PuppetMasterService/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..46b6a97
--- /dev/null
+++ b/tuple-space/PuppetMasterService/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PuppetMasterService")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PuppetMasterService")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("829ac545-bc95-43e8-82e6-8001c5c26c1f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/PuppetMasterService/PuppetMasterService.csproj b/tuple-space/PuppetMasterService/PuppetMasterService.csproj
new file mode 100644
index 0000000..9b16739
--- /dev/null
+++ b/tuple-space/PuppetMasterService/PuppetMasterService.csproj
@@ -0,0 +1,48 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {829AC545-BC95-43E8-82E6-8001C5C26C1F}
+ Library
+ Properties
+ PuppetMasterService
+ PuppetMasterService
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Server/App.config b/tuple-space/Server/App.config
new file mode 100644
index 0000000..96f35e9
--- /dev/null
+++ b/tuple-space/Server/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Server/Program.cs b/tuple-space/Server/Program.cs
new file mode 100644
index 0000000..4afd55d
--- /dev/null
+++ b/tuple-space/Server/Program.cs
@@ -0,0 +1,58 @@
+using System;
+
+using MessageService;
+using StateMachineReplication;
+using StateMachineReplicationAdvanced;
+using XuLiskov;
+using XuLiskovAdvanced;
+
+
+namespace Server {
+ public static class Program {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Program));
+
+ public static void Main(string[] args) {
+ if (args.Length != 5) {
+ Log.Fatal("Expected: Server server_id URL min_delay max_delay protocol");
+ Environment.Exit(1);
+ }
+
+ string serverId = args[0];
+ Uri url = new Uri(args[1]);
+ string protocol = args[4];
+
+ IProtocol protocolToUse = null;
+ if (protocol.Equals("SMR")) {
+ Log.Info("Using State Machine Replication protocol.");
+ protocolToUse = new SMRProtocol();
+ } else if (protocol.Equals("SMRA")) {
+ Log.Info("Using State Machine Replication Advanced protocol.");
+ protocolToUse = new SMRAProtocol();
+ } else if (protocol.Equals("XL")) {
+ Log.Info("Using Xu-Liskov protocol.");
+ protocolToUse = new XLProtocol();
+ } else if(protocol.Equals("XLA")) {
+ Log.Info("Using Xu-Liskov Advanced protocol.");
+ protocolToUse = new XLAProtocol();
+ } else {
+ Log.Fatal("Unknown protocol.");
+ Environment.Exit(1);
+ }
+
+ // create message service wrapper
+ ServerMessageWrapper serverMessage = new ServerMessageWrapper(
+ url,
+ protocolToUse,
+ int.Parse(args[2]),
+ int.Parse(args[3]));
+
+ // init ProtocolUsed
+ protocolToUse.Init(serverMessage.ServiceClient, url, serverId);
+
+ // create puppet master service
+ PuppetMasterServer puppetMasterServer = new PuppetMasterServer(protocolToUse, serverMessage);
+
+ Console.ReadLine();
+ }
+ }
+}
diff --git a/tuple-space/Server/Properties/AssemblyInfo.cs b/tuple-space/Server/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4986e05
--- /dev/null
+++ b/tuple-space/Server/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Server")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Server")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2a311a66-c414-4d66-84de-ba7b361e99e7")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/Server/PuppetMasterService.cs b/tuple-space/Server/PuppetMasterService.cs
new file mode 100644
index 0000000..1d7fa49
--- /dev/null
+++ b/tuple-space/Server/PuppetMasterService.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.Remoting;
+using MessageService;
+using PuppetMasterService;
+
+namespace Server {
+ public class PuppetMasterServer : MarshalByRefObject, IPuppetMasterService {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(PuppetMasterServer));
+
+ private readonly IProtocol protocol;
+ private readonly ServerMessageWrapper serverMessageLayer;
+
+ public PuppetMasterServer(IProtocol protocol, ServerMessageWrapper serverMessageLayer) {
+ this.protocol = protocol;
+ this.serverMessageLayer = serverMessageLayer;
+
+ // Register Service
+ RemotingServices.Marshal(
+ this,
+ PuppetMasterService.Constants.PUPPET_MASTER_SERVICE,
+ typeof(PuppetMasterServer));
+ }
+
+ public void Crash() {
+ Log.Fatal("Crash command issued.");
+ Environment.Exit(1);
+ }
+
+ public void Freeze() {
+ Log.Info("Freeze command issued.");
+ this.serverMessageLayer.Freeze();
+ }
+
+ public void Unfreeze() {
+ Log.Info("Unfreeze command issued.");
+ this.serverMessageLayer.Unfreeze();
+ }
+
+ public string Status() {
+ Log.Info("Status command issued.");
+ string status =
+ $"-------------------------------- MESSAGE LAYER -------------------------------{Environment.NewLine}" +
+ $"{this.serverMessageLayer.Status()}" +
+ $"------------------------------- PROTOCOL LAYER -------------------------------{Environment.NewLine}" +
+ $"{this.protocol.Status()}" +
+ $"=============================================================================={Environment.NewLine}";
+ return status;
+ }
+
+ public override object InitializeLifetimeService() {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/Server/Server.csproj b/tuple-space/Server/Server.csproj
new file mode 100644
index 0000000..95ca95a
--- /dev/null
+++ b/tuple-space/Server/Server.csproj
@@ -0,0 +1,88 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {2A311A66-C414-4D66-84DE-BA7B361E99E7}
+ Exe
+ Server
+ Server
+ v4.6.1
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {13222cd1-28c6-4869-a653-a11cf61bc95a}
+ MessageService
+
+
+ {829ac545-bc95-43e8-82e6-8001c5c26c1f}
+ PuppetMasterService
+
+
+ {507E8427-542F-4BBC-97BE-F047A8EE3CE6}
+ StateMachineReplicationAdvanced
+
+
+ {E8251D83-5C96-45C2-8798-0A15B498F016}
+ StateMachineReplication
+
+
+ {F4029D6B-A26A-4683-8B8E-A53F005797AE}
+ XuLiskovAdvanced
+
+
+ {BA1545BE-E24A-4B05-A58B-F97DB60E1F5C}
+ XuLiskov
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Server/Server.csproj.user b/tuple-space/Server/Server.csproj.user
new file mode 100644
index 0000000..6eb7b88
--- /dev/null
+++ b/tuple-space/Server/Server.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ s1 tcp://localhost:8080 0 0 SMR
+
+
\ No newline at end of file
diff --git a/tuple-space/Server/log4net.config.xml b/tuple-space/Server/log4net.config.xml
new file mode 100644
index 0000000..b008e73
--- /dev/null
+++ b/tuple-space/Server/log4net.config.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/Server/packages.config b/tuple-space/Server/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/Server/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/OrderedQueue.cs b/tuple-space/StateMachineReplication/OrderedQueue.cs
new file mode 100644
index 0000000..6dceacc
--- /dev/null
+++ b/tuple-space/StateMachineReplication/OrderedQueue.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using MessageService;
+using MessageService.Serializable;
+
+namespace StateMachineReplication {
+ public class OrderedQueue {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(OrderedQueue));
+
+ private readonly BlockingCollection requestsToExecute;
+ private readonly EventWaitHandle handler;
+ private int lastOpNumber;
+
+ private readonly ReplicaState replicaState;
+
+ public OrderedQueue(ReplicaState replicaState) {
+ this.requestsToExecute = new BlockingCollection();
+ this.handler = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.lastOpNumber = 0;
+ this.replicaState = replicaState;
+ }
+
+ ///
+ /// Adds an element in the queue. It BLOCKS the op number of the element isn't the
+ /// next op number of the last added element. If less it returns.
+ ///
+ /// The element to add.
+ public void Add(ClientRequest clientRequest, Executor requestToExecute) {
+ if (requestToExecute.OpNumber <= this.lastOpNumber) {
+ return;
+ }
+
+ Log.Debug($"Execution Queue: Request OpNumber #{requestToExecute.OpNumber}, LastOpNumber #{lastOpNumber}.");
+ while (this.lastOpNumber != requestToExecute.OpNumber - 1) {
+ this.handler.WaitOne();
+ }
+
+ this.requestsToExecute.Add(requestToExecute);
+ this.lastOpNumber = requestToExecute.OpNumber;
+
+ // Update Client Table With status execution
+ replicaState.ClientTable[clientRequest.ClientId] =
+ new Tuple(clientRequest.RequestNumber, requestToExecute);
+
+
+ Log.Debug($"Added request #{requestToExecute.OpNumber} to Execution Queue.");
+
+ // notify all waiting threads
+ this.handler.Set();
+ this.handler.Reset();
+ replicaState.HandlersClient.TryGetValue(clientRequest.ClientId, out EventWaitHandle handler);
+ if (handler != null) {
+ handler.Set();
+ handler.Reset();
+ }
+ }
+
+ ///
+ /// Takes the element in the head of the queue. It BLOCKS until there is an element
+ /// in the queue or the expires.
+ ///
+ /// The element in the top of the queue
+ public bool TryTake(out Executor executor, int timeout) {
+ return this.requestsToExecute.TryTake(out executor, timeout);
+ }
+
+ public static void AddRequestToQueue(ReplicaState replicaState, ClientRequest clientRequest, Executor clientExecutor) {
+ if (!replicaState.ClientTable.TryGetValue(clientRequest.ClientId, out Tuple clientTableCell) ||
+ clientTableCell == null) {
+ // Not in dictionary... Add with value as null
+ replicaState.ClientTable.Add(clientRequest.ClientId, new Tuple(-1, null));
+ }
+
+ lock (clientExecutor) {
+ if (clientExecutor.AddedToQueue) {
+ Log.Debug($"Request #{clientExecutor.OpNumber} is scheduled to join Execution Queue.");
+ return;
+ }
+
+ clientExecutor.AddedToQueue = true;
+ }
+ // Add to execution queue
+ replicaState.ExecutionQueue.Add(clientRequest, clientExecutor);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/Properties/AssemblyInfo.cs b/tuple-space/StateMachineReplication/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d36abae
--- /dev/null
+++ b/tuple-space/StateMachineReplication/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("StateMachineReplication")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("StateMachineReplication")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("e8251d83-5c96-45c2-8798-0a15b498f016")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/StateMachineReplication/ReplicaState.cs b/tuple-space/StateMachineReplication/ReplicaState.cs
new file mode 100644
index 0000000..d4cd8e7
--- /dev/null
+++ b/tuple-space/StateMachineReplication/ReplicaState.cs
@@ -0,0 +1,291 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+
+using StateMachineReplication.StateProcessor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication {
+
+ public class ReplicaState {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ReplicaState));
+ public MessageServiceClient MessageServiceClient { get; }
+
+ // SMR protocol state
+ public string ServerId { get; }
+ public Uri MyUrl { get; }
+
+ public SortedDictionary Configuration { get; private set; }
+ public List ReplicasUrl { get; private set; }
+ public List Logger { get; private set; }
+ public OrderedQueue ExecutionQueue { get; }
+ public Dictionary> ClientTable { get; }
+
+ public string Leader { get; set; }
+ public IMessageSMRVisitor State { get; set; }
+
+ // Attribute Atomic operations
+ public int ViewNumber { get; private set; }
+
+ private int opNumber;
+ public int OpNumber => this.opNumber;
+ public int IncrementOpNumberNumber() { return Interlocked.Increment(ref this.opNumber); }
+
+ private int commitNumber;
+ public int CommitNumber => this.commitNumber;
+ public int IncrementCommitNumber() { return Interlocked.Increment(ref this.commitNumber); }
+
+ // Tuple Space
+ public TupleSpace.TupleSpace TupleSpace { get; }
+
+ // Request SMRExecutor
+ private readonly RequestsExecutor requestsExecutor;
+
+ // HeartBeats
+ public SortedDictionary HeartBeats { get; set; }
+
+ // Wait Handlers
+ public EventWaitHandle HandlerStateChanged { get; }
+ public EventWaitHandle HandlersCommits { get; }
+ public EventWaitHandle HandlersPrepare { get; }
+ public ConcurrentDictionary HandlersClient { get; }
+
+ public ReplicaState(MessageServiceClient messageServiceClient, Uri url, string serverId) {
+ this.MessageServiceClient = messageServiceClient;
+ this.ServerId = serverId;
+ this.MyUrl = url;
+
+ this.Configuration = new SortedDictionary { { this.ServerId, url } };
+ this.ReplicasUrl = new List();
+ this.Logger = new List();
+ this.ClientTable = new Dictionary>();
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.ViewNumber = 0;
+ this.opNumber = 0;
+ this.commitNumber = 0;
+ this.ExecutionQueue = new OrderedQueue(this);
+ this.TupleSpace = new TupleSpace.TupleSpace();
+ this.requestsExecutor = new RequestsExecutor(this, this.MessageServiceClient);
+ this.HeartBeats = new SortedDictionary();
+
+
+ // Handlers
+ this.HandlerStateChanged = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersCommits = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersPrepare = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersClient = new ConcurrentDictionary();
+
+ // Task that executes the requests.
+ Task.Factory.StartNew(() => {
+ while (true) {
+ if (this.ExecutionQueue.TryTake(out Executor requestToExecute, Timeout.TIMEOUT_RECOVERY)) {
+ requestToExecute.Execute(this.requestsExecutor);
+ } else {
+ this.ChangeToRecoveryState();
+ }
+ }
+ });
+
+ // Task that checks HeartBeats
+ Task.Factory.StartNew(() => {
+ while (true) {
+ Thread.Sleep(Timeout.TIMEOUT_VIEW_CHANGE);
+ foreach (KeyValuePair entry in this.HeartBeats) {
+ if (entry.Value < DateTime.Now.AddMilliseconds(-Timeout.TIMEOUT_HEART_BEAT * 1.1)) {
+ int newViewNumber = this.ViewNumber + 1;
+ SortedDictionary newConfiguration = new SortedDictionary(
+ this.Configuration
+ .Where(kvp => !kvp.Key.Equals(entry.Key))
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
+
+ Log.Debug($"Server {entry.Key} is presumably dead as the HeartBeat timeout expired.");
+ this.ChangeToViewChange(newViewNumber, newConfiguration);
+ break;
+ }
+ }
+ }
+ });
+
+ // Task that sends the HeartBeats
+ Task.Factory.StartNew(() => {
+ while (true) {
+ Thread.Sleep(Timeout.TIMEOUT_HEART_BEAT);
+ if (!(this.State is NormalStateMessageProcessor)) {
+ continue;
+ }
+ Task.Factory.StartNew(() => this.MessageServiceClient.RequestMulticast(
+ new HeartBeat(this.ServerId),
+ this.ReplicasUrl.ToArray(),
+ this.ReplicasUrl.Count,
+ -1,
+ false));
+ }
+ });
+ }
+
+ public bool IAmTheLeader() {
+ return string.Equals(this.ServerId, this.Leader);
+ }
+
+ public void SetNewConfiguration(SortedDictionary configuration, Uri[] replicasUrl, int newViewNumber) {
+ this.Configuration = configuration;
+ this.ReplicasUrl = replicasUrl.ToList();
+ this.Leader = this.Configuration.Keys.ToArray()[0];
+ this.ViewNumber = newViewNumber;
+ this.UpdateOpNumber();
+ }
+
+ public void SetNewConfiguration(
+ SortedDictionary configuration,
+ Uri[] replicasUrl,
+ int newViewNumber,
+ List logger,
+ int opNumber,
+ int commitNumber) {
+ Log.Warn($"Changing configuration: entering view #{newViewNumber}");
+
+ this.Configuration = configuration;
+ this.ReplicasUrl = replicasUrl.ToList();
+ this.Leader = this.Configuration.Keys.ToArray()[0];
+ this.ViewNumber = newViewNumber;
+ this.Logger = logger;
+ this.UpdateOpNumber();
+
+ // Create HeartBeat dictionary with entries at DateTime.Now
+ DateTime now = DateTime.Now;
+ this.HeartBeats = new SortedDictionary(
+ configuration.Where(kvp => kvp.Key != this.ServerId).ToDictionary(kvp => kvp.Key, kvp => now));
+
+ // Execute all requests until the received commitNumber
+ this.ExecuteFromUntil(this.commitNumber, commitNumber);
+
+ // Execute Missing and send Commit Message
+ if (this.IAmTheLeader()) {
+ this.ExecuteFromUntil(commitNumber, opNumber);
+ }
+ }
+
+ public void ExecuteFromUntil(int begin, int end) {
+ Task.Factory.StartNew(() => {
+ for (int i = begin; i < end; i++) {
+ Executor clientExecutor = ExecutorFactory.Factory(this.Logger[i], i + 1);
+
+ // Add request to queue
+ Log.Debug($"Trying to add request #{opNumber} in the Execution Queue");
+ OrderedQueue.AddRequestToQueue(this, this.Logger[i], clientExecutor);
+ }
+ });
+ }
+
+ public void RestartInitializationState() {
+ lock (this.State) {
+ if (this.State is InitializationStateMessageProcessor) {
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToInitializationState() {
+ lock (this.State) {
+ if (!(this.State is InitializationStateMessageProcessor)) {
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToNormalState() {
+ lock (this.State) {
+ if (!(this.State is NormalStateMessageProcessor)) {
+ this.State = new NormalStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(int newViewNumber, SortedDictionary configuration) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, newViewNumber, configuration);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(StartChange startChange) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, startChange);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(DoViewChange doViewChange) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, doViewChange);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToRecoveryState() {
+ lock (this.State) {
+ if (this.State is NormalStateMessageProcessor) {
+ this.State = new RecoveryStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public string Status() {
+ StringBuilder status = new StringBuilder();
+ status.Append(
+ $"Server ID: {this.ServerId} {Environment.NewLine}" +
+ $"Leader: {this.Leader} {Environment.NewLine}" +
+ $"State: {this.State} {Environment.NewLine}" +
+ $"Op Number: {this.opNumber} {Environment.NewLine}" +
+ $"Commit Number: {this.commitNumber} {Environment.NewLine}" +
+ $"View Number: {this.ViewNumber} {Environment.NewLine}" +
+ $"{"View Configuration:",10} {"Server ID",-10} {"URL",-10} {Environment.NewLine}");
+
+ foreach (KeyValuePair entry in this.Configuration) {
+ status.Append($"{" ",10} {entry.Key,-10} {entry.Value,-10} {Environment.NewLine}");
+ }
+
+ status.Append(
+ $"----------------------------- TUPLE SPACE LAYER ------------------------------{Environment.NewLine}");
+ status.Append(this.TupleSpace.Status());
+
+ return status.ToString();
+ }
+
+ public void UpdateOpNumber() {
+ this.opNumber = this.Logger.Count;
+ this.HandlersPrepare.Set();
+ this.HandlersPrepare.Reset();
+ }
+
+ public void UpdateHeartBeat(string serverId) {
+ if (this.HeartBeats.ContainsKey(serverId)) {
+ this.HeartBeats[serverId] = DateTime.Now;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/RequestsExecutor.cs b/tuple-space/StateMachineReplication/RequestsExecutor.cs
new file mode 100644
index 0000000..80e8a26
--- /dev/null
+++ b/tuple-space/StateMachineReplication/RequestsExecutor.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+
+namespace StateMachineReplication {
+ public class RequestsExecutor : IExecutorVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RequestsExecutor));
+
+ private readonly ReplicaState replicaState;
+ private readonly MessageServiceClient messageServiceClient;
+
+ public RequestsExecutor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.replicaState = replicaState;
+ this.messageServiceClient = messageServiceClient;
+ }
+
+ public void ExecuteAdd(AddExecutor addExecutor) {
+ Log.Debug($"Requesting Add({addExecutor.Tuple}) to Tuple Space.");
+ this.replicaState.TupleSpace.Add(addExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse = new ClientResponse(commitNumber, viewNumber, string.Empty);
+
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[addExecutor.ClientId] =
+ new Tuple(addExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ addExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ public void ExecuteTake(TakeExecutor takeExecutor) {
+ Log.Debug($"Requesting Take({takeExecutor.Tuple}) to Tuple Space.");
+ TupleSpace.Tuple takeTuple = this.replicaState.TupleSpace.Take(takeExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse;
+ if (takeTuple == null) {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, null);
+ } else {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, takeTuple.ToString());
+ }
+
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[takeExecutor.ClientId] =
+ new Tuple(takeExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ takeExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ public void ExecuteRead(ReadExecutor readExecutor) {
+ Log.Debug($"Requesting Read({readExecutor.Tuple}) to Tuple Space.");
+ TupleSpace.Tuple readTuple = this.replicaState.TupleSpace.Read(readExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse;
+ if (readTuple == null) {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, null);
+ } else {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, readTuple.ToString());
+ }
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[readExecutor.ClientId] =
+ new Tuple(readExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ readExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ private void SendCommit(int viewNumber, int commitNumber) {
+ if (!this.replicaState.IAmTheLeader()) {
+ return;
+ }
+ Uri[] replicasUrls;
+
+ lock (this.replicaState) {
+ replicasUrls = this.replicaState.ReplicasUrl.ToArray();
+ }
+
+ CommitMessage commitMessage = new CommitMessage(this.replicaState.ServerId, viewNumber, commitNumber);
+
+ // Don't wait for response
+ Task.Factory.StartNew(() =>
+ this.messageServiceClient.RequestMulticast(commitMessage, replicasUrls, replicasUrls.Length, -1, false));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/SMRProtocol.cs b/tuple-space/StateMachineReplication/SMRProtocol.cs
new file mode 100644
index 0000000..2a12de6
--- /dev/null
+++ b/tuple-space/StateMachineReplication/SMRProtocol.cs
@@ -0,0 +1,36 @@
+using System;
+
+using log4net;
+
+using MessageService;
+
+namespace StateMachineReplication {
+
+ public class SMRProtocol : IProtocol {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(SMRProtocol));
+
+ public ReplicaState ReplicaState { get; private set; }
+
+ public void Init(MessageServiceClient messageServiceClient, Uri url, string serverId) {
+ this.ReplicaState = new ReplicaState(messageServiceClient, url, serverId);
+ }
+
+ public string Status() {
+ string status;
+ lock (this.ReplicaState) {
+ status =
+ $"Protocol: State Machine Replication {Environment.NewLine}" +
+ $"{this.ReplicaState.Status()}";
+ }
+ return status;
+ }
+
+ public bool QueueWhenFrozen() {
+ return false;
+ }
+
+ public IResponse ProcessRequest(IMessage message) {
+ return message.Accept(this.ReplicaState.State);
+ }
+ }
+}
diff --git a/tuple-space/StateMachineReplication/StateMachineReplication.csproj b/tuple-space/StateMachineReplication/StateMachineReplication.csproj
new file mode 100644
index 0000000..0e1c06a
--- /dev/null
+++ b/tuple-space/StateMachineReplication/StateMachineReplication.csproj
@@ -0,0 +1,68 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {E8251D83-5C96-45C2-8798-0A15B498F016}
+ Library
+ Properties
+ StateMachineReplication
+ StateMachineReplication
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {13222CD1-28C6-4869-A653-A11CF61BC95A}
+ MessageService
+
+
+ {F3FB05ED-40A7-4C96-B94B-B926893CFF7C}
+ TupleSpace
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/StateProcessor/InitializationStateMessageProcessor.cs b/tuple-space/StateMachineReplication/StateProcessor/InitializationStateMessageProcessor.cs
new file mode 100644
index 0000000..c018684
--- /dev/null
+++ b/tuple-space/StateMachineReplication/StateProcessor/InitializationStateMessageProcessor.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication.StateProcessor {
+ public class InitializationStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(InitializationStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ private const string SERVERS_LIST = "..\\..\\..\\servers.txt";
+
+ public InitializationStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Initialization State.");
+
+ Task.Factory.StartNew(this.InitProtocol);
+
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ return this.WaitNormalState(startViewChange);
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(doViewChange);
+ }
+ }
+ return doViewChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ Log.Info($"Start Change issued from server {startChange.ServerId}");
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(startChange);
+ }
+ }
+ return startChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ return this.WaitNormalState(recovery);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void InitProtocol() {
+ Uri[] servers = System.IO.File.ReadAllLines(SERVERS_LIST).ToList()
+ .ConvertAll(server => new Uri(server))
+ .Where(server => !server.Equals(this.replicaState.MyUrl))
+ .ToArray();
+
+ IMessage message = new ServerHandShakeRequest(this.replicaState.ServerId, Protocol.StateMachineReplication);
+ IResponses responses =
+ this.messageServiceClient.RequestMulticast(message, servers, 1, Timeout.TIMEOUT_SERVER_HANDSHAKE, true);
+
+ IResponse[] filteredResponses = responses.ToArray();
+
+ // I'm the first server
+ if (filteredResponses.Length == 0) {
+ Log.Info("No servers found for handshake.");
+ this.replicaState.SetNewConfiguration(
+ new SortedDictionary { { this.replicaState.ServerId, this.replicaState.MyUrl } },
+ new Uri[] { },
+ this.replicaState.ViewNumber + 1);
+ this.replicaState.ChangeToNormalState();
+ return;
+ }
+
+ // Else multicast joinView to everyone
+ Uri[] configuration = ((ServerHandShakeResponse)filteredResponses[0]).ViewConfiguration;
+ IMessage joinViewMessage = new JoinView(this.replicaState.ServerId, this.replicaState.MyUrl);
+ this.messageServiceClient.RequestMulticast(
+ joinViewMessage,
+ configuration,
+ configuration.Length,
+ Timeout.TIMEOUT_INIT,
+ false);
+ }
+
+ private void StartTimeout() {
+ Thread.Sleep(Timeout.TIMEOUT_INIT);
+ if (this.Equals(this.replicaState.State)) {
+ // Initialization not successful
+ Log.Info("Initialization was not successful. Trying again.");
+ this.replicaState.RestartInitializationState();
+ }
+ }
+
+ public override string ToString() {
+ return "Initialization";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/StateProcessor/NormalStateMessageProcessor.cs b/tuple-space/StateMachineReplication/StateProcessor/NormalStateMessageProcessor.cs
new file mode 100644
index 0000000..fd49434
--- /dev/null
+++ b/tuple-space/StateMachineReplication/StateProcessor/NormalStateMessageProcessor.cs
@@ -0,0 +1,303 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Remoting.Messaging;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication.StateProcessor {
+ internal enum ProcessRequest { DROP, LAST_EXECUTION}
+
+ public class NormalStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(NormalStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ public NormalStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Normal State.");
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(addRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[addRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(takeRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[takeRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(readRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[readRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ if (prepareMessage.ViewNumber < this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.replicaState.OpNumber >= prepareMessage.OpNumber) {
+ return new PrepareOk(this.replicaState.ServerId, this.replicaState.ViewNumber, prepareMessage.OpNumber);
+ }
+
+ // It must wait for previous messages.
+ while (this.replicaState.OpNumber != (prepareMessage.OpNumber - 1)) {
+ if (prepareMessage.ViewNumber < this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.replicaState.OpNumber >= prepareMessage.OpNumber - 1) {
+ return new PrepareOk(this.replicaState.ServerId, this.replicaState.ViewNumber, prepareMessage.OpNumber);
+ }
+ this.replicaState.HandlersPrepare.WaitOne();
+ }
+
+ int opNumber;
+ int replicaView;
+ lock (this.replicaState) {
+ replicaView = this.replicaState.ViewNumber;
+ opNumber = this.replicaState.IncrementOpNumberNumber();
+ this.replicaState.Logger.Add(prepareMessage.ClientRequest);
+ }
+
+ // Notify all threads that are waiting for new prepare messages
+ this.replicaState.HandlersPrepare.Set();
+ this.replicaState.HandlersPrepare.Reset();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ return new PrepareOk(this.replicaState.ServerId, replicaView, opNumber);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ Task.Factory.StartNew(() => {
+ if (commitMessage.CommitNumber < this.replicaState.CommitNumber &&
+ commitMessage.ViewNumber != this.replicaState.ViewNumber &&
+ commitMessage.ServerId != this.replicaState.Leader) {
+ return;
+ }
+
+ // It must confirm that it received the prepare message.
+ while (commitMessage.CommitNumber > this.replicaState.OpNumber) {
+ this.replicaState.HandlersCommits.WaitOne();
+ }
+
+ this.replicaState.ExecuteFromUntil(this.replicaState.CommitNumber, commitMessage.CommitNumber);
+ });
+ return null;
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ if (this.replicaState.HandlerStateChanged.WaitOne(Timeout.TIMEOUT_VIEW_CHANGE) &&
+ this.replicaState.State is ViewChangeMessageProcessor) {
+ return startViewChange.Accept(this.replicaState.State);
+ }
+ return null;
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this.replicaState.State) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(doViewChange.ViewNumber, doViewChange.Configuration);
+ }
+ }
+ return doViewChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this.replicaState.State) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(startChange);
+ }
+ }
+ return startChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ if (this.replicaState.OpNumber < recovery.OpNumber) {
+ return new RecoveryResponse(this.replicaState.ServerId);
+ }
+
+ int count = this.replicaState.Logger.Count;
+
+ return new RecoveryResponse(
+ this.replicaState.ServerId,
+ this.replicaState.ViewNumber,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber,
+ this.replicaState.Logger.GetRange(recovery.OpNumber, count - recovery.OpNumber));
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ Uri[] viewConfiguration = this.replicaState.Configuration.Values.ToArray();
+ return new ClientHandShakeResponse(
+ Protocol.StateMachineReplication,
+ this.replicaState.ViewNumber,
+ viewConfiguration,
+ this.replicaState.Configuration[this.replicaState.Leader]);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return new ServerHandShakeResponse(this.replicaState.Configuration.Values.ToArray());
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ Log.Info($"JoinView issued for server {joinView.ServerId}");
+ int newViewNumber = this.replicaState.ViewNumber + 1;
+ if (this.replicaState.Configuration.ContainsKey(joinView.ServerId)) {
+ return null;
+ }
+ SortedDictionary newConfiguration = new SortedDictionary(this.replicaState.Configuration);
+ newConfiguration.Add(joinView.ServerId, joinView.Url);
+
+ this.replicaState.ChangeToViewChange(newViewNumber, newConfiguration);
+ return null;
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ private ProcessRequest RunProcessRequestProtocol(ClientRequest clientRequest) {
+ if (this.replicaState.ClientTable.TryGetValue(clientRequest.ClientId, out Tuple clientResponse)) {
+ // Key is in the dictionary
+ if (clientResponse == null || clientResponse.Item1 < 0 ||
+ clientRequest.RequestNumber < clientResponse.Item1) {
+ // Duplicate Request: Long forgotten => drop
+ return ProcessRequest.DROP;
+ }
+
+ if (clientRequest.RequestNumber == clientResponse.Item1) {
+ // Duplicate Request
+ // If it is in execution.. wait.
+ if (clientResponse.Item2.GetType() == typeof(Executor)) {
+ Executor executor = (Executor)clientResponse.Item2;
+ executor.Executed.WaitOne();
+ }
+ return ProcessRequest.LAST_EXECUTION;
+ }
+
+ // Execute the requests in client's casual order
+ if (clientRequest.RequestNumber != clientResponse.Item1 + 1) {
+ if (!this.replicaState.HandlersClient.ContainsKey(clientRequest.ClientId)) {
+ this.replicaState.HandlersClient.TryAdd(
+ clientRequest.ClientId,
+ new EventWaitHandle(false, EventResetMode.ManualReset));
+ }
+
+ this.replicaState.HandlersClient.TryGetValue(
+ clientRequest.ClientId,
+ out EventWaitHandle myHandler);
+ while (clientRequest.RequestNumber != clientResponse.Item1 + 1) {
+ if (clientRequest.RequestNumber > clientResponse.Item1 + 1) {
+ return ProcessRequest.DROP;
+ }
+ myHandler.WaitOne();
+ }
+ }
+ } else {
+ // Not in dictionary... Add with value as null
+ this.replicaState.ClientTable.Add(clientRequest.ClientId, new Tuple(-1, null));
+ }
+
+ // Send Prepare Message and waits for f replies. opNumber is the order we agreed upon.
+ int opNumber = this.SendPrepareMessage(clientRequest);
+ Executor clientExecutor = ExecutorFactory.Factory(clientRequest, opNumber);
+
+ // Add request to queue
+ OrderedQueue.AddRequestToQueue(this.replicaState, clientRequest, clientExecutor);
+
+ // Wait execution
+ clientExecutor.Executed.WaitOne();
+
+ return ProcessRequest.LAST_EXECUTION;
+ }
+
+ private int SendPrepareMessage(ClientRequest clientRequest) {
+ int viewNumber;
+ int opNumber;
+ int commitNumber;
+ Uri[] replicasUrls;
+
+
+ lock (this.replicaState) {
+ viewNumber = this.replicaState.ViewNumber;
+ commitNumber = this.replicaState.CommitNumber;
+ opNumber = this.replicaState.IncrementOpNumberNumber();
+ this.replicaState.Logger.Add(clientRequest);
+ replicasUrls = this.replicaState.ReplicasUrl.ToArray();
+ }
+
+ PrepareMessage prepareMessage = new PrepareMessage(
+ this.replicaState.ServerId,
+ viewNumber,
+ clientRequest,
+ opNumber,
+ commitNumber);
+
+ // Wait for (Number backup replicas + the leader) / 2
+ int f = this.replicaState.Configuration.Count / 2;
+ this.messageServiceClient.RequestMulticast(prepareMessage, replicasUrls, f, -1, true);
+
+ return opNumber;
+ }
+
+ public override string ToString() {
+ return "Normal";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/StateProcessor/RecoveryStateMessageProcessor.cs b/tuple-space/StateMachineReplication/StateProcessor/RecoveryStateMessageProcessor.cs
new file mode 100644
index 0000000..7a71488
--- /dev/null
+++ b/tuple-space/StateMachineReplication/StateProcessor/RecoveryStateMessageProcessor.cs
@@ -0,0 +1,145 @@
+using System.Security.Principal;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication.StateProcessor {
+ public class RecoveryStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RecoveryStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ public RecoveryStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Recovery State.");
+
+ // Start the protocol
+ Task.Factory.StartNew(this.RecoveryProtocol);
+
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ return this.WaitNormalState(startViewChange);
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ return this.WaitNormalState(doViewChange);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ return this.WaitNormalState(startChange);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ return this.WaitNormalState(recovery);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void RecoveryProtocol() {
+ // Multicast recovery message
+ IMessage message = new Recovery(
+ this.replicaState.ServerId,
+ this.replicaState.ViewNumber,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ IResponses responses = this.messageServiceClient.RequestMulticast(
+ message,
+ this.replicaState.ReplicasUrl.ToArray(),
+ this.replicaState.Configuration.Count / 2,
+ -1,
+ true);
+ Log.Debug($"Recovery Protocol: got {responses.Count()} responses.");
+ if (responses.Count() == 0) {
+ this.replicaState.ChangeToInitializationState();
+ return;
+ }
+
+ RecoveryResponse betterResponse = null;
+ foreach (IResponse response in responses.ToArray()) {
+ RecoveryResponse recoveryResponse = (RecoveryResponse)response;
+ if (recoveryResponse.ViewNumber > this.replicaState.ViewNumber) {
+ this.replicaState.ChangeToInitializationState();
+ return;
+ }
+
+ if (recoveryResponse.ViewNumber == this.replicaState.ViewNumber) {
+ if (betterResponse == null) {
+ betterResponse = recoveryResponse;
+ continue;
+ }
+
+ if (recoveryResponse.OpNumber > betterResponse.OpNumber) {
+ betterResponse = recoveryResponse;
+ }
+ }
+ }
+
+ if (betterResponse != null &&
+ betterResponse.OpNumber > this.replicaState.OpNumber) {
+ Log.Debug($"Better Response: OpNumber = {betterResponse.OpNumber}, " +
+ $"CommitNumber = {betterResponse.CommitNumber} ({this.replicaState.CommitNumber})");
+
+ this.replicaState.Logger.AddRange(betterResponse.SuffixLogger);
+ this.replicaState.UpdateOpNumber();
+ this.replicaState.ExecuteFromUntil(this.replicaState.CommitNumber, betterResponse.CommitNumber);
+
+ }
+ Log.Debug($"Recovery Protocol: Changing to Normal State.");
+ this.replicaState.ChangeToNormalState();
+ }
+
+ public override string ToString() {
+ return "Recovery";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/StateProcessor/ViewChangeMessageProcessor.cs b/tuple-space/StateMachineReplication/StateProcessor/ViewChangeMessageProcessor.cs
new file mode 100644
index 0000000..0c1231c
--- /dev/null
+++ b/tuple-space/StateMachineReplication/StateProcessor/ViewChangeMessageProcessor.cs
@@ -0,0 +1,308 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using StateMachineReplication.Utils;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication.StateProcessor {
+ public class ViewChangeMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ViewChangeMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ private readonly int viewNumber;
+ private readonly SortedDictionary configuration;
+ private readonly bool imTheLeader;
+
+
+ private readonly int numberToWait;
+ private int messagesDoViewChange;
+
+ private DoViewChange bestDoViewChange;
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ int viewNumber,
+ SortedDictionary configuration) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = viewNumber;
+ this.configuration = configuration;
+
+ this.imTheLeader = this.configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = this.replicaState.Configuration.Count / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Start the view change protocol
+ Task.Factory.StartNew(this.MulticastStartViewChange);
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+
+ }
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ StartChange startChange) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = startChange.ViewNumber;
+ this.configuration = startChange.Configuration;
+
+ this.imTheLeader = startChange.Configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = (startChange.Configuration.Count - 1) / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ DoViewChange doViewChange) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = doViewChange.ViewNumber;
+ this.configuration = doViewChange.Configuration;
+
+ this.imTheLeader = doViewChange.Configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = (doViewChange.Configuration.Count - 1) / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ if (startViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (startViewChange.ViewNumber == this.viewNumber &&
+ ConfigurationUtils.CompareConfigurations(startViewChange.Configuration, this.configuration)) {
+ return new StartViewChangeOk(this.replicaState.ServerId, this.viewNumber, this.configuration);
+ }
+ Log.Debug("Received Start View Change that don't match.");
+ return null;
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.imTheLeader &&
+ doViewChange.ViewNumber == this.viewNumber &&
+ ConfigurationUtils.CompareConfigurations(doViewChange.Configuration, this.configuration)) {
+ Interlocked.Increment(ref this.messagesDoViewChange);
+
+ if (doViewChange.OpNumber > this.bestDoViewChange.OpNumber) {
+ this.bestDoViewChange = doViewChange;
+ }
+
+
+ this.CheckNumberAndSetNewConfiguration();
+ }
+
+ return null;
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ // Set new configuration
+ Uri[] replicasUrl = this.configuration.Values
+ .Where(url => !url.Equals(this.replicaState.MyUrl))
+ .ToArray();
+ this.replicaState.SetNewConfiguration(
+ startChange.Configuration,
+ replicasUrl,
+ startChange.ViewNumber,
+ startChange.Logger,
+ startChange.OpNumber,
+ startChange.CommitNumber);
+ this.replicaState.ChangeToNormalState();
+ return null;
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return recovery.Accept(this.replicaState.State);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void MulticastStartViewChange() {
+ IMessage message = new StartViewChange(this.replicaState.ServerId, this.viewNumber, this.configuration);
+ Uri[] currentConfiguration = this.replicaState.ReplicasUrl.ToArray();
+
+ IResponses responses = this.messageServiceClient.RequestMulticast(
+ message,
+ currentConfiguration,
+ this.replicaState.Configuration.Count / 2,
+ (int)(Timeout.TIMEOUT_VIEW_CHANGE),
+ true);
+
+ IResponse[] responsesVector = responses.ToArray();
+
+ // There was no quorum to accept the view change
+ if (responsesVector.Length < this.numberToWait) {
+ Log.Debug($"There was no quorum for view change. " +
+ $"Just received {responsesVector.Length} from at least {this.numberToWait}");
+ this.replicaState.ChangeToNormalState();
+ return;
+ }
+
+ // In case I'm the leader, wait for f DoViewChange
+ if (this.imTheLeader) {
+ this.CheckNumberAndSetNewConfiguration();
+ } else {
+ // Else, send DoViewChange to leader
+ Uri leader = this.configuration.Values.ToArray()[0];
+ IMessage doViewMessage = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ this.messageServiceClient.Request(doViewMessage, leader, -1);
+ }
+ }
+
+
+ private void CheckNumberAndSetNewConfiguration() {
+ if (this.messagesDoViewChange >= this.numberToWait) {
+ // start change
+ Uri[] replicasUrl = this.configuration.Values
+ .Where(url => !url.Equals(this.replicaState.MyUrl))
+ .ToArray();
+
+ IMessage message = new StartChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.configuration,
+ this.bestDoViewChange.Logger,
+ this.bestDoViewChange.OpNumber,
+ this.bestDoViewChange.CommitNumber);
+ Task.Factory.StartNew(() =>
+ this.messageServiceClient.RequestMulticast(message, replicasUrl, replicasUrl.Length, -1, false));
+
+ // Set new configuration
+ this.replicaState.SetNewConfiguration(
+ this.bestDoViewChange.Configuration,
+ replicasUrl,
+ this.bestDoViewChange.ViewNumber,
+ this.bestDoViewChange.Logger,
+ this.bestDoViewChange.OpNumber,
+ this.bestDoViewChange.CommitNumber);
+
+ this.replicaState.ChangeToRecoveryState();
+ }
+ }
+
+ private void StartTimeout() {
+ Thread.Sleep((int)(Timeout.TIMEOUT_VIEW_CHANGE));
+ if (this.Equals(this.replicaState.State)) {
+ // View Change was not successful, return to normal
+ Log.Debug("View Change was not successful.");
+ this.replicaState.ChangeToNormalState();
+ }
+ }
+
+ public override string ToString() {
+ return "View Change";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplication/packages.config b/tuple-space/StateMachineReplication/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/StateMachineReplication/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/OrderedQueue.cs b/tuple-space/StateMachineReplicationAdvanced/OrderedQueue.cs
new file mode 100644
index 0000000..3ae66d6
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/OrderedQueue.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using MessageService;
+using MessageService.Serializable;
+
+namespace StateMachineReplicationAdvanced {
+ public class OrderedQueue {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(OrderedQueue));
+
+ private readonly BlockingCollection requestsToExecute;
+ private readonly EventWaitHandle handler;
+ private int lastOpNumber;
+
+ private readonly ReplicaState replicaState;
+
+ public OrderedQueue(ReplicaState replicaState) {
+ this.requestsToExecute = new BlockingCollection();
+ this.handler = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.lastOpNumber = 0;
+ this.replicaState = replicaState;
+ }
+
+ ///
+ /// Adds an element in the queue. It BLOCKS the op number of the element isn't the
+ /// next op number of the last added element. If less it returns.
+ ///
+ /// The element to add.
+ public void Add(ClientRequest clientRequest, Executor requestToExecute) {
+ if (requestToExecute.OpNumber <= this.lastOpNumber) {
+ return;
+ }
+
+ Log.Debug($"Execution Queue: Request OpNumber #{requestToExecute.OpNumber}, LastOpNumber #{lastOpNumber}.");
+ while (this.lastOpNumber != requestToExecute.OpNumber - 1) {
+ this.handler.WaitOne();
+ }
+
+ this.requestsToExecute.Add(requestToExecute);
+ this.lastOpNumber = requestToExecute.OpNumber;
+
+ // Update Client Table With status execution
+ replicaState.ClientTable[clientRequest.ClientId] =
+ new Tuple(clientRequest.RequestNumber, requestToExecute);
+
+
+ Log.Debug($"Added request #{requestToExecute.OpNumber} to Execution Queue.");
+
+ // notify all waiting threads
+ this.handler.Set();
+ this.handler.Reset();
+ replicaState.HandlersClient.TryGetValue(clientRequest.ClientId, out EventWaitHandle handler);
+ if (handler != null) {
+ handler.Set();
+ handler.Reset();
+ }
+ }
+
+ ///
+ /// Takes the element in the head of the queue. It BLOCKS until there is an element
+ /// in the queue or the expires.
+ ///
+ /// The element in the top of the queue
+ public bool TryTake(out Executor executor, int timeout) {
+ return this.requestsToExecute.TryTake(out executor, timeout);
+ }
+
+ public static void AddRequestToQueue(ReplicaState replicaState, ClientRequest clientRequest, Executor clientExecutor) {
+ if (!replicaState.ClientTable.TryGetValue(clientRequest.ClientId, out Tuple clientTableCell) ||
+ clientTableCell == null) {
+ // Not in dictionary... Add with value as null
+ replicaState.ClientTable.Add(clientRequest.ClientId, new Tuple(-1, null));
+ }
+
+ lock (clientExecutor) {
+ if (clientExecutor.AddedToQueue) {
+ Log.Debug($"Request #{clientExecutor.OpNumber} is scheduled to join Execution Queue.");
+ return;
+ }
+
+ clientExecutor.AddedToQueue = true;
+ }
+ // Add to execution queue
+ replicaState.ExecutionQueue.Add(clientRequest, clientExecutor);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/Properties/AssemblyInfo.cs b/tuple-space/StateMachineReplicationAdvanced/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ad95302
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/Properties/AssemblyInfo.cs
@@ -0,0 +1,37 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("StateMachineReplicationAdvanced")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("StateMachineReplicationAdvanced")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config.xml", Watch = true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("507e8427-542f-4bbc-97be-f047a8ee3ce6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/StateMachineReplicationAdvanced/ReplicaState.cs b/tuple-space/StateMachineReplicationAdvanced/ReplicaState.cs
new file mode 100644
index 0000000..400e38d
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/ReplicaState.cs
@@ -0,0 +1,291 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using StateMachineReplication.StateProcessor;
+using StateMachineReplicationAdvanced.StateProcessor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplicationAdvanced {
+
+ public class ReplicaState {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ReplicaState));
+ public MessageServiceClient MessageServiceClient { get; }
+
+ // SMR protocol state
+ public string ServerId { get; }
+ public Uri MyUrl { get; }
+
+ public SortedDictionary Configuration { get; private set; }
+ public List ReplicasUrl { get; private set; }
+ public List Logger { get; private set; }
+ public OrderedQueue ExecutionQueue { get; }
+ public Dictionary> ClientTable { get; }
+
+ public string Leader { get; set; }
+ public IMessageSMRVisitor State { get; set; }
+
+ // Attribute Atomic operations
+ public int ViewNumber { get; private set; }
+
+ private int opNumber;
+ public int OpNumber => this.opNumber;
+ public int IncrementOpNumberNumber() { return Interlocked.Increment(ref this.opNumber); }
+
+ private int commitNumber;
+ public int CommitNumber => this.commitNumber;
+ public int IncrementCommitNumber() { return Interlocked.Increment(ref this.commitNumber); }
+
+ // Tuple Space
+ public TupleSpace.TupleSpace TupleSpace { get; }
+
+ // Request SMRExecutor
+ private readonly RequestsExecutor requestsExecutor;
+
+ // HeartBeats
+ public SortedDictionary HeartBeats { get; set; }
+
+ // Wait Handlers
+ public EventWaitHandle HandlerStateChanged { get; }
+ public EventWaitHandle HandlersCommits { get; }
+ public EventWaitHandle HandlersPrepare { get; }
+ public ConcurrentDictionary HandlersClient { get; }
+
+ public ReplicaState(MessageServiceClient messageServiceClient, Uri url, string serverId) {
+ this.MessageServiceClient = messageServiceClient;
+ this.ServerId = serverId;
+ this.MyUrl = url;
+
+ this.Configuration = new SortedDictionary { { this.ServerId, url } };
+ this.ReplicasUrl = new List();
+ this.Logger = new List();
+ this.ClientTable = new Dictionary>();
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.ViewNumber = 0;
+ this.opNumber = 0;
+ this.commitNumber = 0;
+ this.ExecutionQueue = new OrderedQueue(this);
+ this.TupleSpace = new TupleSpace.TupleSpace();
+ this.requestsExecutor = new RequestsExecutor(this, this.MessageServiceClient);
+ this.HeartBeats = new SortedDictionary();
+
+
+ // Handlers
+ this.HandlerStateChanged = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersCommits = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersPrepare = new EventWaitHandle(false, EventResetMode.ManualReset);
+ this.HandlersClient = new ConcurrentDictionary();
+
+ // Task that executes the requests.
+ Task.Factory.StartNew(() => {
+ while (true) {
+ if (this.ExecutionQueue.TryTake(out Executor requestToExecute, Timeout.TIMEOUT_RECOVERY)) {
+ requestToExecute.Execute(this.requestsExecutor);
+ } else {
+ this.ChangeToRecoveryState();
+ }
+ }
+ });
+
+ // Task that checks HeartBeats
+ Task.Factory.StartNew(() => {
+ while (true) {
+ Thread.Sleep(Timeout.TIMEOUT_VIEW_CHANGE);
+ foreach (KeyValuePair entry in this.HeartBeats) {
+ if (entry.Value < DateTime.Now.AddMilliseconds(-Timeout.TIMEOUT_HEART_BEAT * 1.1)) {
+ int newViewNumber = this.ViewNumber + 1;
+ SortedDictionary newConfiguration = new SortedDictionary(
+ this.Configuration
+ .Where(kvp => !kvp.Key.Equals(entry.Key))
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
+
+ Log.Debug($"Server {entry.Key} is presumably dead as the HeartBeat timeout expired.");
+ this.ChangeToViewChange(newViewNumber, newConfiguration);
+ break;
+ }
+ }
+ }
+ });
+
+ // Task that sends the HeartBeats
+ Task.Factory.StartNew(() => {
+ while (true) {
+ Thread.Sleep(Timeout.TIMEOUT_HEART_BEAT);
+ if (!(this.State is NormalStateMessageProcessor)) {
+ continue;
+ }
+ Task.Factory.StartNew(() => this.MessageServiceClient.RequestMulticast(
+ new HeartBeat(this.ServerId),
+ this.ReplicasUrl.ToArray(),
+ this.ReplicasUrl.Count,
+ -1,
+ false));
+ }
+ });
+ }
+
+ public bool IAmTheLeader() {
+ return string.Equals(this.ServerId, this.Leader);
+ }
+
+ public void SetNewConfiguration(SortedDictionary configuration, Uri[] replicasUrl, int newViewNumber) {
+ this.Configuration = configuration;
+ this.ReplicasUrl = replicasUrl.ToList();
+ this.Leader = this.Configuration.Keys.ToArray()[0];
+ this.ViewNumber = newViewNumber;
+ this.UpdateOpNumber();
+ }
+
+ public void SetNewConfiguration(
+ SortedDictionary configuration,
+ Uri[] replicasUrl,
+ int newViewNumber,
+ List logger,
+ int opNumber,
+ int commitNumber) {
+ Log.Warn($"Changing configuration: entering view #{newViewNumber}");
+
+ this.Configuration = configuration;
+ this.ReplicasUrl = replicasUrl.ToList();
+ this.Leader = this.Configuration.Keys.ToArray()[0];
+ this.ViewNumber = newViewNumber;
+ this.Logger = logger;
+ this.UpdateOpNumber();
+
+ // Create HeartBeat dictionary with entries at DateTime.Now
+ DateTime now = DateTime.Now;
+ this.HeartBeats = new SortedDictionary(
+ configuration.Where(kvp => kvp.Key != this.ServerId).ToDictionary(kvp => kvp.Key, kvp => now));
+
+ // Execute all requests until the received commitNumber
+ this.ExecuteFromUntil(this.commitNumber, commitNumber);
+
+ // Execute Missing and send Commit Message
+ if (this.IAmTheLeader()) {
+ this.ExecuteFromUntil(commitNumber, opNumber);
+ }
+ }
+
+ public void ExecuteFromUntil(int begin, int end) {
+ Task.Factory.StartNew(() => {
+ for (int i = begin; i < end; i++) {
+ Executor clientExecutor = ExecutorFactory.Factory(this.Logger[i], i + 1);
+
+ // Add request to queue
+ Log.Debug($"Trying to add request #{opNumber} in the Execution Queue");
+ OrderedQueue.AddRequestToQueue(this, this.Logger[i], clientExecutor);
+ }
+ });
+ }
+
+ public void RestartInitializationState() {
+ lock (this.State) {
+ if (this.State is InitializationStateMessageProcessor) {
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToInitializationState() {
+ lock (this.State) {
+ if (!(this.State is InitializationStateMessageProcessor)) {
+ this.State = new InitializationStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToNormalState() {
+ lock (this.State) {
+ if (!(this.State is NormalStateMessageProcessor)) {
+ this.State = new NormalStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(int newViewNumber, SortedDictionary configuration) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, newViewNumber, configuration);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(StartChange startChange) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, startChange);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToViewChange(DoViewChange doViewChange) {
+ lock (this.State) {
+ if (!(this.State is ViewChangeMessageProcessor)) {
+ this.State = new ViewChangeMessageProcessor(this.MessageServiceClient, this, doViewChange);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public void ChangeToRecoveryState() {
+ lock (this.State) {
+ if (this.State is NormalStateMessageProcessor) {
+ this.State = new RecoveryStateMessageProcessor(this, this.MessageServiceClient);
+ this.HandlerStateChanged.Set();
+ this.HandlerStateChanged.Reset();
+ }
+ }
+ }
+
+ public string Status() {
+ StringBuilder status = new StringBuilder();
+ status.Append(
+ $"Server ID: {this.ServerId} {Environment.NewLine}" +
+ $"Leader: {this.Leader} {Environment.NewLine}" +
+ $"State: {this.State} {Environment.NewLine}" +
+ $"Op Number: {this.opNumber} {Environment.NewLine}" +
+ $"Commit Number: {this.commitNumber} {Environment.NewLine}" +
+ $"View Number: {this.ViewNumber} {Environment.NewLine}" +
+ $"{"View Configuration:",10} {"Server ID",-10} {"URL",-10} {Environment.NewLine}");
+
+ foreach (KeyValuePair entry in this.Configuration) {
+ status.Append($"{" ",10} {entry.Key,-10} {entry.Value,-10} {Environment.NewLine}");
+ }
+
+ status.Append(
+ $"----------------------------- TUPLE SPACE LAYER ------------------------------{Environment.NewLine}");
+ status.Append(this.TupleSpace.Status());
+
+ return status.ToString();
+ }
+
+ public void UpdateOpNumber() {
+ this.opNumber = this.Logger.Count;
+ this.HandlersPrepare.Set();
+ this.HandlersPrepare.Reset();
+ }
+
+ public void UpdateHeartBeat(string serverId) {
+ if (this.HeartBeats.ContainsKey(serverId)) {
+ this.HeartBeats[serverId] = DateTime.Now;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/RequestsExecutor.cs b/tuple-space/StateMachineReplicationAdvanced/RequestsExecutor.cs
new file mode 100644
index 0000000..d40cbfa
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/RequestsExecutor.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+
+namespace StateMachineReplicationAdvanced {
+ public class RequestsExecutor : IExecutorVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RequestsExecutor));
+
+ private readonly ReplicaState replicaState;
+ private readonly MessageServiceClient messageServiceClient;
+
+ public RequestsExecutor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.replicaState = replicaState;
+ this.messageServiceClient = messageServiceClient;
+ }
+
+ public void ExecuteAdd(AddExecutor addExecutor) {
+ Log.Debug($"Requesting Add({addExecutor.Tuple}) to Tuple Space.");
+ this.replicaState.TupleSpace.Add(addExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse = new ClientResponse(commitNumber, viewNumber, string.Empty);
+
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[addExecutor.ClientId] =
+ new Tuple(addExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ addExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ public void ExecuteTake(TakeExecutor takeExecutor) {
+ Log.Debug($"Requesting Take({takeExecutor.Tuple}) to Tuple Space.");
+ TupleSpace.Tuple takeTuple = this.replicaState.TupleSpace.Take(takeExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse;
+ if (takeTuple == null) {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, null);
+ } else {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, takeTuple.ToString());
+ }
+
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[takeExecutor.ClientId] =
+ new Tuple(takeExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ takeExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ public void ExecuteRead(ReadExecutor readExecutor) {
+ Log.Debug($"Requesting Read({readExecutor.Tuple}) to Tuple Space.");
+ TupleSpace.Tuple readTuple = this.replicaState.TupleSpace.Read(readExecutor.Tuple);
+
+ // increment commit number
+ int commitNumber = this.replicaState.IncrementCommitNumber();
+ int viewNumber = this.replicaState.ViewNumber;
+
+ ClientResponse clientResponse;
+ if (readTuple == null) {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, null);
+ } else {
+ clientResponse = new ClientResponse(commitNumber, viewNumber, readTuple.ToString());
+ }
+ // update client table
+ lock (this.replicaState) {
+ this.replicaState.ClientTable[readExecutor.ClientId] =
+ new Tuple(readExecutor.RequestNumber, clientResponse);
+ }
+
+ // Signal waiting thread that the execution is done
+ readExecutor.Executed.Set();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ // commit execution
+ this.SendCommit(viewNumber, commitNumber);
+ }
+
+ private void SendCommit(int viewNumber, int commitNumber) {
+ if (!this.replicaState.IAmTheLeader()) {
+ return;
+ }
+ Uri[] replicasUrls;
+
+ lock (this.replicaState) {
+ replicasUrls = this.replicaState.ReplicasUrl.ToArray();
+ }
+
+ CommitMessage commitMessage = new CommitMessage(this.replicaState.ServerId, viewNumber, commitNumber);
+
+ // Don't wait for response
+ Task.Factory.StartNew(() =>
+ this.messageServiceClient.RequestMulticast(commitMessage, replicasUrls, replicasUrls.Length, -1, false));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/SMRAProtocol.cs b/tuple-space/StateMachineReplicationAdvanced/SMRAProtocol.cs
new file mode 100644
index 0000000..bf85d63
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/SMRAProtocol.cs
@@ -0,0 +1,36 @@
+using System;
+
+using log4net;
+
+using MessageService;
+
+namespace StateMachineReplicationAdvanced {
+
+ public class SMRAProtocol : IProtocol {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(SMRAProtocol));
+
+ public ReplicaState ReplicaState { get; private set; }
+
+ public void Init(MessageServiceClient messageServiceClient, Uri url, string serverId) {
+ this.ReplicaState = new ReplicaState(messageServiceClient, url, serverId);
+ }
+
+ public string Status() {
+ string status;
+ lock (this.ReplicaState) {
+ status =
+ $"Protocol: State Machine Replication {Environment.NewLine}" +
+ $"{this.ReplicaState.Status()}";
+ }
+ return status;
+ }
+
+ public bool QueueWhenFrozen() {
+ return false;
+ }
+
+ public IResponse ProcessRequest(IMessage message) {
+ return message.Accept(this.ReplicaState.State);
+ }
+ }
+}
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj b/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj
new file mode 100644
index 0000000..6a4ad77
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj
@@ -0,0 +1,68 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {507E8427-542F-4BBC-97BE-F047A8EE3CE6}
+ Library
+ Properties
+ StateMachineReplicationAdvanced
+ StateMachineReplicationAdvanced
+ v4.6.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {13222cd1-28c6-4869-a653-a11cf61bc95a}
+ MessageService
+
+
+ {f3fb05ed-40a7-4c96-b94b-b926893cff7c}
+ TupleSpace
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj.user b/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj.user
new file mode 100644
index 0000000..cc8c43f
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateMachineReplicationAdvanced.csproj.user
@@ -0,0 +1,6 @@
+
+
+
+ 1 tcp://localhost:8080 0 0 SMR
+
+
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateProcessor/InitializationStateMessageProcessor.cs b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/InitializationStateMessageProcessor.cs
new file mode 100644
index 0000000..3950457
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/InitializationStateMessageProcessor.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using StateMachineReplicationAdvanced;
+using StateMachineReplicationAdvanced.StateProcessor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplication.StateProcessor {
+ public class InitializationStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(InitializationStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ private const string SERVERS_LIST = "..\\..\\..\\servers.txt";
+
+ public InitializationStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Initialization State.");
+
+ Task.Factory.StartNew(this.InitProtocol);
+
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ return this.WaitNormalState(startViewChange);
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(doViewChange);
+ }
+ }
+ return doViewChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ Log.Info($"Start Change issued from server {startChange.ServerId}");
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(startChange);
+ }
+ }
+ return startChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ return this.WaitNormalState(recovery);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void InitProtocol() {
+ Uri[] servers = System.IO.File.ReadAllLines(SERVERS_LIST).ToList()
+ .ConvertAll(server => new Uri(server))
+ .Where(server => !server.Equals(this.replicaState.MyUrl))
+ .ToArray();
+
+ IMessage message = new ServerHandShakeRequest(this.replicaState.ServerId, Protocol.StateMachineReplication);
+ IResponses responses =
+ this.messageServiceClient.RequestMulticast(message, servers, 1, Timeout.TIMEOUT_SERVER_HANDSHAKE, true);
+
+ IResponse[] filteredResponses = responses.ToArray();
+
+ // I'm the first server
+ if (filteredResponses.Length == 0) {
+ Log.Info("No servers found for handshake.");
+ this.replicaState.SetNewConfiguration(
+ new SortedDictionary { { this.replicaState.ServerId, this.replicaState.MyUrl } },
+ new Uri[] { },
+ this.replicaState.ViewNumber + 1);
+ this.replicaState.ChangeToNormalState();
+ return;
+ }
+
+ // Else multicast joinView to everyone
+ Uri[] configuration = ((ServerHandShakeResponse)filteredResponses[0]).ViewConfiguration;
+ IMessage joinViewMessage = new JoinView(this.replicaState.ServerId, this.replicaState.MyUrl);
+ this.messageServiceClient.RequestMulticast(
+ joinViewMessage,
+ configuration,
+ configuration.Length,
+ Timeout.TIMEOUT_INIT,
+ false);
+ }
+
+ private void StartTimeout() {
+ Thread.Sleep(Timeout.TIMEOUT_INIT);
+ if (this.Equals(this.replicaState.State)) {
+ // Initialization not successful
+ Log.Info("Initialization was not successful. Trying again.");
+ this.replicaState.RestartInitializationState();
+ }
+ }
+
+ public override string ToString() {
+ return "Initialization";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateProcessor/NormalStateMessageProcessor.cs b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/NormalStateMessageProcessor.cs
new file mode 100644
index 0000000..0f003e1
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/NormalStateMessageProcessor.cs
@@ -0,0 +1,302 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplicationAdvanced.StateProcessor {
+ internal enum ProcessRequest { DROP, LAST_EXECUTION}
+
+ public class NormalStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(NormalStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ public NormalStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Normal State.");
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(addRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[addRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(takeRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[takeRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ if (!this.replicaState.IAmTheLeader()) {
+ // I'm not the leader.
+ return null;
+ }
+
+ ProcessRequest runProcessRequestProtocol = this.RunProcessRequestProtocol(readRequest);
+ if (runProcessRequestProtocol == ProcessRequest.DROP) {
+ return null;
+ }
+
+ if (runProcessRequestProtocol == ProcessRequest.LAST_EXECUTION) {
+ return this.replicaState.ClientTable[readRequest.ClientId].Item2;
+ }
+
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ if (prepareMessage.ViewNumber < this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.replicaState.OpNumber >= prepareMessage.OpNumber) {
+ return new PrepareOk(this.replicaState.ServerId, this.replicaState.ViewNumber, prepareMessage.OpNumber);
+ }
+
+ // It must wait for previous messages.
+ while (this.replicaState.OpNumber != (prepareMessage.OpNumber - 1)) {
+ if (prepareMessage.ViewNumber < this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.replicaState.OpNumber >= prepareMessage.OpNumber - 1) {
+ return new PrepareOk(this.replicaState.ServerId, this.replicaState.ViewNumber, prepareMessage.OpNumber);
+ }
+ this.replicaState.HandlersPrepare.WaitOne();
+ }
+
+ int opNumber;
+ int replicaView;
+ lock (this.replicaState) {
+ replicaView = this.replicaState.ViewNumber;
+ opNumber = this.replicaState.IncrementOpNumberNumber();
+ this.replicaState.Logger.Add(prepareMessage.ClientRequest);
+ }
+
+ // Notify all threads that are waiting for new prepare messages
+ this.replicaState.HandlersPrepare.Set();
+ this.replicaState.HandlersPrepare.Reset();
+ this.replicaState.HandlersCommits.Set();
+ this.replicaState.HandlersCommits.Reset();
+
+ return new PrepareOk(this.replicaState.ServerId, replicaView, opNumber);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ Task.Factory.StartNew(() => {
+ if (commitMessage.CommitNumber < this.replicaState.CommitNumber &&
+ commitMessage.ViewNumber != this.replicaState.ViewNumber &&
+ commitMessage.ServerId != this.replicaState.Leader) {
+ return;
+ }
+
+ // It must confirm that it received the prepare message.
+ while (commitMessage.CommitNumber > this.replicaState.OpNumber) {
+ this.replicaState.HandlersCommits.WaitOne();
+ }
+
+ this.replicaState.ExecuteFromUntil(this.replicaState.CommitNumber, commitMessage.CommitNumber);
+ });
+ return null;
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ if (this.replicaState.HandlerStateChanged.WaitOne(Timeout.TIMEOUT_VIEW_CHANGE) &&
+ this.replicaState.State is ViewChangeMessageProcessor) {
+ return startViewChange.Accept(this.replicaState.State);
+ }
+ return null;
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this.replicaState.State) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(doViewChange.ViewNumber, doViewChange.Configuration);
+ }
+ }
+ return doViewChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ lock (this.replicaState.State) {
+ if (!(this.replicaState.State is ViewChangeMessageProcessor)) {
+ this.replicaState.ChangeToViewChange(startChange);
+ }
+ }
+ return startChange.Accept(this.replicaState.State);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ if (this.replicaState.OpNumber < recovery.OpNumber) {
+ return new RecoveryResponse(this.replicaState.ServerId);
+ }
+
+ int count = this.replicaState.Logger.Count;
+
+ return new RecoveryResponse(
+ this.replicaState.ServerId,
+ this.replicaState.ViewNumber,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber,
+ this.replicaState.Logger.GetRange(recovery.OpNumber, count - recovery.OpNumber));
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ Uri[] viewConfiguration = this.replicaState.Configuration.Values.ToArray();
+ return new ClientHandShakeResponse(
+ Protocol.StateMachineReplication,
+ this.replicaState.ViewNumber,
+ viewConfiguration,
+ this.replicaState.Configuration[this.replicaState.Leader]);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return new ServerHandShakeResponse(this.replicaState.Configuration.Values.ToArray());
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ Log.Info($"JoinView issued for server {joinView.ServerId}");
+ int newViewNumber = this.replicaState.ViewNumber + 1;
+ if (this.replicaState.Configuration.ContainsKey(joinView.ServerId)) {
+ return null;
+ }
+ SortedDictionary newConfiguration = new SortedDictionary(this.replicaState.Configuration);
+ newConfiguration.Add(joinView.ServerId, joinView.Url);
+
+ this.replicaState.ChangeToViewChange(newViewNumber, newConfiguration);
+ return null;
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ private ProcessRequest RunProcessRequestProtocol(ClientRequest clientRequest) {
+ if (this.replicaState.ClientTable.TryGetValue(clientRequest.ClientId, out Tuple clientResponse)) {
+ // Key is in the dictionary
+ if (clientResponse == null || clientResponse.Item1 < 0 ||
+ clientRequest.RequestNumber < clientResponse.Item1) {
+ // Duplicate Request: Long forgotten => drop
+ return ProcessRequest.DROP;
+ }
+
+ if (clientRequest.RequestNumber == clientResponse.Item1) {
+ // Duplicate Request
+ // If it is in execution.. wait.
+ if (clientResponse.Item2.GetType() == typeof(Executor)) {
+ Executor executor = (Executor)clientResponse.Item2;
+ executor.Executed.WaitOne();
+ }
+ return ProcessRequest.LAST_EXECUTION;
+ }
+
+ // Execute the requests in client's casual order
+ if (clientRequest.RequestNumber != clientResponse.Item1 + 1) {
+ if (!this.replicaState.HandlersClient.ContainsKey(clientRequest.ClientId)) {
+ this.replicaState.HandlersClient.TryAdd(
+ clientRequest.ClientId,
+ new EventWaitHandle(false, EventResetMode.ManualReset));
+ }
+
+ this.replicaState.HandlersClient.TryGetValue(
+ clientRequest.ClientId,
+ out EventWaitHandle myHandler);
+ while (clientRequest.RequestNumber != clientResponse.Item1 + 1) {
+ if (clientRequest.RequestNumber > clientResponse.Item1 + 1) {
+ return ProcessRequest.DROP;
+ }
+ myHandler.WaitOne();
+ }
+ }
+ } else {
+ // Not in dictionary... Add with value as null
+ this.replicaState.ClientTable.Add(clientRequest.ClientId, new Tuple(-1, null));
+ }
+
+ // Send Prepare Message and waits for f replies. opNumber is the order we agreed upon.
+ int opNumber = this.SendPrepareMessage(clientRequest);
+ Executor clientExecutor = ExecutorFactory.Factory(clientRequest, opNumber);
+
+ // Add request to queue
+ OrderedQueue.AddRequestToQueue(this.replicaState, clientRequest, clientExecutor);
+
+ // Wait execution
+ clientExecutor.Executed.WaitOne();
+
+ return ProcessRequest.LAST_EXECUTION;
+ }
+
+ private int SendPrepareMessage(ClientRequest clientRequest) {
+ int viewNumber;
+ int opNumber;
+ int commitNumber;
+ Uri[] replicasUrls;
+
+
+ lock (this.replicaState) {
+ viewNumber = this.replicaState.ViewNumber;
+ commitNumber = this.replicaState.CommitNumber;
+ opNumber = this.replicaState.IncrementOpNumberNumber();
+ this.replicaState.Logger.Add(clientRequest);
+ replicasUrls = this.replicaState.ReplicasUrl.ToArray();
+ }
+
+ PrepareMessage prepareMessage = new PrepareMessage(
+ this.replicaState.ServerId,
+ viewNumber,
+ clientRequest,
+ opNumber,
+ commitNumber);
+
+ // Wait for (Number backup replicas + the leader) / 2
+ int f = this.replicaState.Configuration.Count / 2;
+ this.messageServiceClient.RequestMulticast(prepareMessage, replicasUrls, f, -1, true);
+
+ return opNumber;
+ }
+
+ public override string ToString() {
+ return "Normal";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateProcessor/RecoveryStateMessageProcessor.cs b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/RecoveryStateMessageProcessor.cs
new file mode 100644
index 0000000..6c6178b
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/RecoveryStateMessageProcessor.cs
@@ -0,0 +1,142 @@
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+
+namespace StateMachineReplicationAdvanced.StateProcessor {
+ public class RecoveryStateMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(RecoveryStateMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ public RecoveryStateMessageProcessor(ReplicaState replicaState, MessageServiceClient messageServiceClient) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ Log.Info("Changed to Recovery State.");
+
+ // Start the protocol
+ Task.Factory.StartNew(this.RecoveryProtocol);
+
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ return this.WaitNormalState(startViewChange);
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ return this.WaitNormalState(doViewChange);
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ return this.WaitNormalState(startChange);
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ return this.WaitNormalState(recovery);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void RecoveryProtocol() {
+ // Multicast recovery message
+ IMessage message = new Recovery(
+ this.replicaState.ServerId,
+ this.replicaState.ViewNumber,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ IResponses responses = this.messageServiceClient.RequestMulticast(
+ message,
+ this.replicaState.ReplicasUrl.ToArray(),
+ this.replicaState.Configuration.Count / 2,
+ -1,
+ true);
+ Log.Debug($"Recovery Protocol: got {responses.Count()} responses.");
+ if (responses.Count() == 0) {
+ this.replicaState.ChangeToInitializationState();
+ return;
+ }
+
+ RecoveryResponse betterResponse = null;
+ foreach (IResponse response in responses.ToArray()) {
+ RecoveryResponse recoveryResponse = (RecoveryResponse)response;
+ if (recoveryResponse.ViewNumber > this.replicaState.ViewNumber) {
+ this.replicaState.ChangeToInitializationState();
+ return;
+ }
+
+ if (recoveryResponse.ViewNumber == this.replicaState.ViewNumber) {
+ if (betterResponse == null) {
+ betterResponse = recoveryResponse;
+ continue;
+ }
+
+ if (recoveryResponse.OpNumber > betterResponse.OpNumber) {
+ betterResponse = recoveryResponse;
+ }
+ }
+ }
+
+ if (betterResponse != null &&
+ betterResponse.OpNumber > this.replicaState.OpNumber) {
+ Log.Debug($"Better Response: OpNumber = {betterResponse.OpNumber}, " +
+ $"CommitNumber = {betterResponse.CommitNumber} ({this.replicaState.CommitNumber})");
+
+ this.replicaState.Logger.AddRange(betterResponse.SuffixLogger);
+ this.replicaState.UpdateOpNumber();
+ this.replicaState.ExecuteFromUntil(this.replicaState.CommitNumber, betterResponse.CommitNumber);
+
+ }
+ Log.Debug($"Recovery Protocol: Changing to Normal State.");
+ this.replicaState.ChangeToNormalState();
+ }
+
+ public override string ToString() {
+ return "Recovery";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/StateProcessor/ViewChangeMessageProcessor.cs b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/ViewChangeMessageProcessor.cs
new file mode 100644
index 0000000..95e0c2f
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/StateProcessor/ViewChangeMessageProcessor.cs
@@ -0,0 +1,306 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MessageService;
+using MessageService.Serializable;
+using MessageService.Visitor;
+using StateMachineReplication.Utils;
+using Timeout = MessageService.Timeout;
+
+namespace StateMachineReplicationAdvanced.StateProcessor {
+ public class ViewChangeMessageProcessor : IMessageSMRVisitor {
+ private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(ViewChangeMessageProcessor));
+
+ private readonly MessageServiceClient messageServiceClient;
+ private readonly ReplicaState replicaState;
+
+ private readonly int viewNumber;
+ private readonly SortedDictionary configuration;
+ private readonly bool imTheLeader;
+
+
+ private readonly int numberToWait;
+ private int messagesDoViewChange;
+
+ private DoViewChange bestDoViewChange;
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ int viewNumber,
+ SortedDictionary configuration) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = viewNumber;
+ this.configuration = configuration;
+
+ this.imTheLeader = this.configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = this.replicaState.Configuration.Count / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Start the view change protocol
+ Task.Factory.StartNew(this.MulticastStartViewChange);
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ StartChange startChange) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = startChange.ViewNumber;
+ this.configuration = startChange.Configuration;
+
+ this.imTheLeader = startChange.Configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = (startChange.Configuration.Count - 1) / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public ViewChangeMessageProcessor(
+ MessageServiceClient messageServiceClient,
+ ReplicaState replicaState,
+ DoViewChange doViewChange) {
+ this.messageServiceClient = messageServiceClient;
+ this.replicaState = replicaState;
+
+ this.viewNumber = doViewChange.ViewNumber;
+ this.configuration = doViewChange.Configuration;
+
+ this.imTheLeader = doViewChange.Configuration.Values.ToArray()[0].Equals(this.replicaState.MyUrl);
+ this.numberToWait = (doViewChange.Configuration.Count - 1) / 2;
+ this.messagesDoViewChange = 0;
+
+ this.bestDoViewChange = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ Log.Info("Changed to View Change State.");
+
+ // Stay in this state for a timeout
+ Task.Factory.StartNew(this.StartTimeout);
+ }
+
+ public IResponse VisitAddRequest(AddRequest addRequest) {
+ return this.WaitNormalState(addRequest);
+ }
+
+ public IResponse VisitTakeRequest(TakeRequest takeRequest) {
+ return this.WaitNormalState(takeRequest);
+ }
+
+ public IResponse VisitReadRequest(ReadRequest readRequest) {
+ return this.WaitNormalState(readRequest);
+ }
+
+ public IResponse VisitClientHandShakeRequest(ClientHandShakeRequest clientHandShakeRequest) {
+ return this.WaitNormalState(clientHandShakeRequest);
+ }
+
+ public IResponse VisitServerHandShakeRequest(ServerHandShakeRequest serverHandShakeRequest) {
+ return this.WaitNormalState(serverHandShakeRequest);
+ }
+
+ public IResponse VisitJoinView(JoinView joinView) {
+ return this.WaitNormalState(joinView);
+ }
+
+ public IResponse VisitHeartBeat(HeartBeat heartBeat) {
+ this.replicaState.UpdateHeartBeat(heartBeat.ServerId);
+ return null;
+ }
+
+ public IResponse VisitPrepareMessage(PrepareMessage prepareMessage) {
+ return this.WaitNormalState(prepareMessage);
+ }
+
+ public IResponse VisitCommitMessage(CommitMessage commitMessage) {
+ return this.WaitNormalState(commitMessage);
+ }
+
+ public IResponse VisitStartViewChange(StartViewChange startViewChange) {
+ if (startViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (startViewChange.ViewNumber == this.viewNumber &&
+ ConfigurationUtils.CompareConfigurations(startViewChange.Configuration, this.configuration)) {
+ return new StartViewChangeOk(this.replicaState.ServerId, this.viewNumber, this.configuration);
+ }
+ Log.Debug("Received Start View Change that don't match.");
+ return null;
+ }
+
+ public IResponse VisitDoViewChange(DoViewChange doViewChange) {
+ if (doViewChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ if (this.imTheLeader &&
+ doViewChange.ViewNumber == this.viewNumber &&
+ ConfigurationUtils.CompareConfigurations(doViewChange.Configuration, this.configuration)) {
+ Interlocked.Increment(ref this.messagesDoViewChange);
+
+ if (doViewChange.OpNumber > this.bestDoViewChange.OpNumber) {
+ this.bestDoViewChange = doViewChange;
+ }
+
+
+ this.CheckNumberAndSetNewConfiguration();
+ }
+
+ return null;
+ }
+
+ public IResponse VisitStartChange(StartChange startChange) {
+ if (startChange.ViewNumber <= this.replicaState.ViewNumber) {
+ return null;
+ }
+ // Set new configuration
+ Uri[] replicasUrl = this.configuration.Values
+ .Where(url => !url.Equals(this.replicaState.MyUrl))
+ .ToArray();
+ this.replicaState.SetNewConfiguration(
+ startChange.Configuration,
+ replicasUrl,
+ startChange.ViewNumber,
+ startChange.Logger,
+ startChange.OpNumber,
+ startChange.CommitNumber);
+ this.replicaState.ChangeToNormalState();
+ return null;
+ }
+
+ public IResponse VisitRecovery(Recovery recovery) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return recovery.Accept(this.replicaState.State);
+ }
+
+ private IResponse WaitNormalState(IMessage message) {
+ while (!(this.replicaState.State is NormalStateMessageProcessor)) {
+ this.replicaState.HandlerStateChanged.WaitOne();
+ }
+ return message.Accept(this.replicaState.State);
+ }
+
+ private void MulticastStartViewChange() {
+ IMessage message = new StartViewChange(this.replicaState.ServerId, this.viewNumber, this.configuration);
+ Uri[] currentConfiguration = this.replicaState.ReplicasUrl.ToArray();
+
+ IResponses responses = this.messageServiceClient.RequestMulticast(
+ message,
+ currentConfiguration,
+ this.replicaState.Configuration.Count / 2,
+ (int)(Timeout.TIMEOUT_VIEW_CHANGE),
+ true);
+
+ IResponse[] responsesVector = responses.ToArray();
+
+ // There was no quorum to accept the view change
+ if (responsesVector.Length < this.numberToWait) {
+ Log.Debug($"There was no quorum for view change. " +
+ $"Just received {responsesVector.Length} from at least {this.numberToWait}");
+ this.replicaState.ChangeToNormalState();
+ return;
+ }
+
+ // In case I'm the leader, wait for f DoViewChange
+ if (this.imTheLeader) {
+ this.CheckNumberAndSetNewConfiguration();
+ } else {
+ // Else, send DoViewChange to leader
+ Uri leader = this.configuration.Values.ToArray()[0];
+ IMessage doViewMessage = new DoViewChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.replicaState.ViewNumber,
+ this.configuration,
+ this.replicaState.Logger,
+ this.replicaState.OpNumber,
+ this.replicaState.CommitNumber);
+
+ this.messageServiceClient.Request(doViewMessage, leader, -1);
+ }
+ }
+
+
+ private void CheckNumberAndSetNewConfiguration() {
+ if (this.messagesDoViewChange >= this.numberToWait) {
+ // start change
+ Uri[] replicasUrl = this.configuration.Values
+ .Where(url => !url.Equals(this.replicaState.MyUrl))
+ .ToArray();
+
+ IMessage message = new StartChange(
+ this.replicaState.ServerId,
+ this.viewNumber,
+ this.configuration,
+ this.bestDoViewChange.Logger,
+ this.bestDoViewChange.OpNumber,
+ this.bestDoViewChange.CommitNumber);
+ Task.Factory.StartNew(() =>
+ this.messageServiceClient.RequestMulticast(message, replicasUrl, replicasUrl.Length, -1, false));
+
+ // Set new configuration
+ this.replicaState.SetNewConfiguration(
+ this.bestDoViewChange.Configuration,
+ replicasUrl,
+ this.bestDoViewChange.ViewNumber,
+ this.bestDoViewChange.Logger,
+ this.bestDoViewChange.OpNumber,
+ this.bestDoViewChange.CommitNumber);
+
+ this.replicaState.ChangeToRecoveryState();
+ }
+ }
+
+ private void StartTimeout() {
+ Thread.Sleep((int) (Timeout.TIMEOUT_VIEW_CHANGE));
+ if (this.Equals(this.replicaState.State)) {
+ // View Change was not successful, return to normal
+ Log.Debug("View Change was not successful.");
+ this.replicaState.ChangeToNormalState();
+ }
+ }
+
+ public override string ToString() {
+ return "View Change";
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/StateMachineReplicationAdvanced/packages.config b/tuple-space/StateMachineReplicationAdvanced/packages.config
new file mode 100644
index 0000000..7554a8a
--- /dev/null
+++ b/tuple-space/StateMachineReplicationAdvanced/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tuple-space/TupleSpace/Exceptions/RequestAlreadyHasLocks.cs b/tuple-space/TupleSpace/Exceptions/RequestAlreadyHasLocks.cs
new file mode 100644
index 0000000..f357c5a
--- /dev/null
+++ b/tuple-space/TupleSpace/Exceptions/RequestAlreadyHasLocks.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace TupleSpace.Exceptions {
+
+ public class RequestAlreadyHasLocks : Exception {
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/TupleSpace/Exceptions/RequestDontHaveLocks.cs b/tuple-space/TupleSpace/Exceptions/RequestDontHaveLocks.cs
new file mode 100644
index 0000000..11bacb2
--- /dev/null
+++ b/tuple-space/TupleSpace/Exceptions/RequestDontHaveLocks.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace TupleSpace.Exceptions {
+
+ public class RequestDontHaveLocks : Exception {
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/TupleSpace/Exceptions/UnableToLockException.cs b/tuple-space/TupleSpace/Exceptions/UnableToLockException.cs
new file mode 100644
index 0000000..d688544
--- /dev/null
+++ b/tuple-space/TupleSpace/Exceptions/UnableToLockException.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace TupleSpace.Exceptions {
+
+ public class UnableToLockException : Exception {
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/TupleSpace/Properties/AssemblyInfo.cs b/tuple-space/TupleSpace/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f3f6a40
--- /dev/null
+++ b/tuple-space/TupleSpace/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("TupleSpace")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("TupleSpace")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f3fb05ed-40a7-4c96-b94b-b926893cff7c")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/tuple-space/TupleSpace/StringUtils.cs b/tuple-space/TupleSpace/StringUtils.cs
new file mode 100644
index 0000000..9a8ab15
--- /dev/null
+++ b/tuple-space/TupleSpace/StringUtils.cs
@@ -0,0 +1,15 @@
+using System.Text.RegularExpressions;
+
+namespace TupleSpace {
+
+ public static class StringUtils {
+
+ public static bool MatchString(string s,string search) {
+ string parsedSearch = "^" + search.Replace("*", ".*") + "$";
+ Regex regex = new Regex(@parsedSearch);
+ Match match = regex.Match(s);
+
+ return match.Success;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tuple-space/TupleSpace/TestClasses.cs b/tuple-space/TupleSpace/TestClasses.cs
new file mode 100644
index 0000000..68211bd
--- /dev/null
+++ b/tuple-space/TupleSpace/TestClasses.cs
@@ -0,0 +1,77 @@
+namespace TupleSpace
+{
+ public class DADTestA
+ {
+ public int i1;
+ public string s1;
+
+ public DADTestA(int pi1, string ps1)
+ {
+ i1 = pi1;
+ s1 = ps1;
+ }
+ public bool Equals(DADTestA o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+ else
+ {
+ return ((this.i1 == o.i1) && (this.s1.Equals(o.s1)));
+ }
+ }
+ }
+
+ public class DADTestB
+ {
+ public int i1;
+ public string s1;
+ public int i2;
+
+ public DADTestB(int pi1, string ps1, int pi2)
+ {
+ i1 = pi1;
+ s1 = ps1;
+ i2 = pi2;
+ }
+
+ public bool Equals(DADTestB o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+ else
+ {
+ return ((this.i1 == o.i1) && (this.s1.Equals(o.s1)) && (this.i2 == o.i2));
+ }
+ }
+ }
+
+ public class DADTestC
+ {
+ public int i1;
+ public string s1;
+ public string s2;
+
+ public DADTestC(int pi1, string ps1, string ps2)
+ {
+ i1 = pi1;
+ s1 = ps1;
+ s2 = ps2;
+ }
+
+ public bool Equals(DADTestC o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+ else
+ {
+ return ((this.i1 == o.i1) && (this.s1.Equals(o.s1)) && (this.s2.Equals(o.s2)));
+ }
+ }
+ }
+}
diff --git a/tuple-space/TupleSpace/Tuple.cs b/tuple-space/TupleSpace/Tuple.cs
new file mode 100644
index 0000000..1d44390
--- /dev/null
+++ b/tuple-space/TupleSpace/Tuple.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace TupleSpace {
+
+ [Serializable]
+ public class Tuple {
+ // A tuple consists in a list of fields.
+ public List Fields { get; set; }
+ public bool Locked { get; set; }
+ private readonly string stringTuple;
+
+ public Tuple(string tuple) {
+ this.Fields = new List();
+ this.Locked = false;
+ this.ParseInput(tuple);
+ this.stringTuple = tuple;
+ }
+
+ public bool Match(Tuple searchTuple) {
+
+ if (this.Fields.Count != searchTuple.Fields.Count) {
+ return false;
+ }
+
+ /* Check all fields of the tuple*/
+ for (int i = 0; i < this.Fields.Count; i++) {
+ /* if field is a string */
+ if (this.Fields[i].Type == typeof(string) && searchTuple.Fields[i].Type == typeof(string) &&
+ StringUtils.MatchString(this.Fields[i].Value.ToString(), searchTuple.Fields[i].Value.ToString())) {
+
+ continue;
+ }
+
+ /* if field is an object */
+ if (this.Fields[i].Type != typeof(string)) {
+
+ /* if search is an object too, check if it is equals */
+ if (searchTuple.Fields[i].Type != typeof(string) &&
+ ((dynamic) this.Fields[i].Value).Equals((dynamic) searchTuple.Fields[i].Value)) {
+
+ continue;
+ }
+ /* if search is a string, check if its the name of the type of the object or null */
+ else if (searchTuple.Fields[i].Type == typeof(string)) {
+ if (searchTuple.Fields[i].Value.Equals("null")) {
+ continue;
+ } else {
+ string fullClassName = string.Concat("TupleSpace.", searchTuple.Fields[i].Value);
+ Type searchType = Type.GetType(fullClassName);
+
+ if (searchType != null && this.Fields[i].Type.Equals(searchType)) {
+ continue;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ public override bool Equals(object obj) {
+ return obj is Tuple tuple && this.stringTuple.Equals(tuple.ToString());
+ }
+
+ public override string ToString() {
+ return this.stringTuple;
+ }
+
+ private void ParseInput(string tuple) {
+
+ List fields = new List(); //stores the parameters of the tuple.
+ string pattern = @"(\"".*?\"")+|((\d)+)|(\w)*?(\(.*?\))|(\w)+";
+ Regex rgx = new Regex(pattern);
+
+ foreach (Match match in rgx.Matches(tuple)) {
+ fields.Add(match.Value);
+ }
+
+ foreach (string field in fields) {
+ /* If the field is a string */
+ if (field.StartsWith("\"", StringComparison.Ordinal)) {
+ /* Add to Tuple fields*/
+ Field newField = new Field(field.Substring(1, field.Length - 2)); //remove the quotes
+ this.Fields.Add(newField);
+ } else { /* The field is an object */
+ char[] charSeparators = { ',', '(', ')' };
+ string[] res = field.Split(charSeparators);
+
+ /* If it's a name - className or null */
+ if (res.Length == 1) {
+ /* Add to Tuple fields*/
+ Field newField = new Field(field);
+ this.Fields.Add(newField);
+ } else { /* It's a constructor */
+ string className = res[0];
+ List args = res.ToList().GetRange(1, res.Length - 2); //removes the className and the last match (empty)
+ List