#!/usr/bin/env dub
/+ dub.sdl:
name "replay_journal"
targetPath "build"
buildType "checked" {
buildOptions "optimize" "inline" "debugInfo"
}
+/
/**
* Durable execution by replay — the mechanism in ~200 lines.
*
* A workflow is an ordinary function that performs its effects through one
* handler, `Durable.step`. Each step has a stable key and an argument
* fingerprint. On a live run the handler executes the step and appends the
* result to an append-only journal. On a resumed run the same function runs
* again from the top; while the journal still has entries the handler answers
* each step from the journal instead of executing it, checking that the key
* and fingerprint match what was recorded, and only once the journal is
* exhausted does it go live again.
*
* The program demonstrates the two properties every system in the catalog
* relies on (see docs/research/algebraic-effects/durable-execution/concepts.md
* § "Replay" and § "Determinism"):
*
* 1. Crash-at-every-index: cutting the journal after any entry and resuming
* re-executes exactly the steps after the cut, and the final journal is
* identical to the uninterrupted run.
* 2. Divergence detection: a step whose key or arguments differ from the
* recorded entry is refused, loudly, instead of silently re-run.
*
* It is an illustration of the replay contract, not a prototype of a library.
*
* Run with: `dub run --single replay-journal.d`
*/
module (module) replay_journalDurable execution by replay — the mechanism in ~200 lines.
A workflow is an ordinary function that performs its effects through one
handler, Durable.step. Each step has a stable key and an argument
fingerprint. On a live run the handler executes the step and appends the
result to an append-only journal. On a resumed run the same function runs
again from the top; while the journal still has entries the handler answers
each step from the journal instead of executing it, checking that the key
and fingerprint match what was recorded, and only once the journal is
exhausted does it go live again.
The program demonstrates the two properties every system in the catalog
relies on (see docs/research/algebraic-effects/durable-execution/concepts.md
§ "Replay" and § "Determinism"):
Crash-at-every-index: cutting the journal after any entry and resuming
re-executes exactly the steps after the cut, and the final journal is
identical to the uninterrupted run.
Divergence detection: a step whose key or arguments differ from the
recorded entry is refused, loudly, instead of silently re-run.
It is an illustration of the replay contract, not a prototype of a library.
Run with: dub run --single replay-journal.d
replay_journal;
import (package) stdstd.(module) std.algorithmThis package implements generic algorithms oriented towards the processing of
sequences. Sequences processed by these functions define range-based
interfaces. See also Reference on ranges and
tutorial on ranges.
Algorithms are categorized into the following submodules:
Submodule Functions
| Searching |
all
any
balancedParens
boyerMooreFinder
canFind
commonPrefix
count
countUntil
endsWith
find
findAdjacent
findAmong
findSkip
findSplit
findSplitAfter
findSplitBefore
minCount
maxCount
minElement
maxElement
minIndex
maxIndex
minPos
maxPos
skipOver
startsWith
until
|
| Comparison |
among
castSwitch
clamp
cmp
either
equal
isPermutation
isSameLength
levenshteinDistance
levenshteinDistanceAndPath
max
min
mismatch
predSwitch
|
| Iteration |
cache
cacheBidirectional
chunkBy
cumulativeFold
each
filter
filterBidirectional
fold
group
joiner
map
mean
permutations
reduce
splitWhen
splitter
substitute
sum
uniq
|
| Sorting |
completeSort
isPartitioned
isSorted
isStrictlyMonotonic
ordered
strictlyOrdered
makeIndex
merge
multiSort
nextEvenPermutation
nextPermutation
nthPermutation
partialSort
partition
partition3
schwartzSort
sort
topN
topNCopy
topNIndex
|
| Set operations (setops) |
cartesianProduct
largestPartialIntersection
largestPartialIntersectionWeighted
multiwayMerge
multiwayUnion
setDifference
setIntersection
setSymmetricDifference
|
| Mutation |
bringToFront
copy
fill
initializeAll
move
moveAll
moveSome
moveEmplace
moveEmplaceAll
moveEmplaceSome
remove
reverse
strip
stripLeft
stripRight
swap
swapRanges
uninitializedFill
|
Many functions in this package are parameterized with a predicate.
The predicate may be any suitable callable type
(a function, a delegate, a functor, or a lambda), or a
compile-time string. The string may consist of any legal D
expression that uses the symbol a (for unary functions) or the
symbols a and b (for binary functions). These names will NOT
interfere with other homonym symbols in user code because they are
evaluated in a different context. The default for all binary
comparison predicates is "a == b" for unordered operations and
"a < b" for ordered operations.
Example
int[] a = ...;
static bool greater(int a, int b)
{
return a > b;
}
sort!greater(a); // predicate as alias
sort!((a, b) => a > b)(a); // predicate as a lambda.
sort!"a > b"(a); // predicate as string
// (no ambiguity with array name)
sort(a); // no predicate, "a < b" is implicit
Source
std/algorithm/package.d
algorithm : (alias template) replay_journal.all = std.algorithm.searching.all(alias pred = "a")Checks if all of the elements satisfy pred.
all, (alias template) replay_journal.equal = std.algorithm.comparison.equal(alias pred = "a == b")Compares two or more ranges for equality, as defined by predicate pred
(which is == by default).
equal, (alias template) replay_journal.map = std.algorithm.iteration.map(fun...) if (fun.length >= 1)Implements the homonym function (also known as transform) present
in many languages of functional flavor. The call ``map!(fun)(range)
returns a range of which elements are obtained by applying fun(a)
left to right for all elements a in range. The original ranges are
not changed. Evaluation is done lazily.
map;
import (package) stdstd.(module) std.arrayFunctions and types that manipulate built-in arrays and associative arrays.
This module provides all kinds of functions to create, manipulate or convert arrays:
Function Name Description
| array |
Returns a copy of the input in a newly allocated dynamic array.
|
| appender |
Returns a new Appender or RefAppender initialized with a given array.
|
| assocArray |
Returns a newly allocated associative array from a range/ranges of keys and values.
|
| byPair |
Construct a range iterating over an associative array by key/value tuples.
|
| insertInPlace |
Inserts into an existing array at a given position.
|
| join |
Concatenates a range of ranges into one array.
|
| minimallyInitializedArray |
Returns a new array of type T.
|
| replace |
Returns a new array with all occurrences of a certain subrange replaced.
|
| replaceFirst |
Returns a new array with the first occurrence of a certain subrange replaced.
|
| replaceInPlace |
Replaces all occurrences of a certain subrange and puts the result into a given array.
|
| replaceInto |
Replaces all occurrences of a certain subrange and puts the result into an output range.
|
| replaceLast |
Returns a new array with the last occurrence of a certain subrange replaced.
|
| replaceSlice |
Returns a new array with a given slice replaced.
|
| replicate |
Creates a new array out of several copies of an input array or range.
|
| sameHead |
Checks if the initial segments of two arrays refer to the same
place in memory.
|
| sameTail |
Checks if the final segments of two arrays refer to the same place
in memory.
|
| split |
Eagerly split a range or string into an array.
|
| staticArray |
Creates a new static array from given data.
|
| uninitializedArray |
Returns a new array of type T without initializing its elements.
|
Source
std/array.d
array : (alias template) replay_journal.array = std.array.array(Range)(Range r) if (isIterable!Range && !isAutodecodableString!Range && !isInfinite!Range)Allocates an array and initializes it with copies of the elements
of range r.
Narrow strings are handled as follows:
If autodecoding is turned on (default), then they are handled as a separate overload.
If autodecoding is turned off, then this is equivalent to duplicating the array.
array;
import (package) stdstd.(module) std.convA one-stop shop for converting values from one type to another.
Category Functions Generic asOriginalType castFrom parse to toChars bitCast Strings text wtext dtext writeText writeWText writeDText hexString Numeric octal roundTo signed unsigned Exceptions ConvException ConvOverflowException
Source
std/conv.d
conv : (alias template) replay_journal.text = std.conv.text(T...)(T args) if (T.length > 0)Convenience functions for converting one or more arguments
of any type into text (the three character widths).
text, (alias template) replay_journal.to = std.conv.to(T)The to template converts a value from one type to another.
The source type is deduced and the target type must be specified, for example the
expression to`!int(42.0)` converts the number 42 from
`double` to `int`. The conversion is "safe", i.e.,
it checks for overflow; to!int(4.2e10) would throw the
ConvOverflowException exception. Overflow checks are only
inserted when necessary, e.g., ``to!double(42) does not do
any checking because any int fits in a double.
Conversions from string to numeric types differ from the C equivalents
atoi() and atol() by checking for overflow and not allowing whitespace.
For conversion of strings to signed types, the grammar recognized is:
Integer:
Sign UnsignedInteger
UnsignedInteger
Sign:
+
-
For conversion to unsigned types, the grammar recognized is:
UnsignedInteger:
DecimalDigit
DecimalDigit UnsignedInteger
to;
import (package) stdstd.(package) std.digestdigest.(module) std.digest.shaComputes SHA1 and SHA2 hashes of arbitrary data. SHA hashes are 20 to 64 byte
quantities (depending on the SHA algorithm) that are like a checksum or CRC,
but are more robust.
Category Functions
| Template API | SHA1 |
| OOP API | SHA1Digest |
| Helpers | sha1Of |
SHA2 comes in several different versions, all supported by this module:
SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224 and SHA-512/256.
This module conforms to the APIs defined in std.digest. To understand the
differences between the template and the OOP API, see std.digest.
This module publicly imports std.digest and can be used as a stand-alone
module.
CTFE
Digests do not work in CTFE
References
Source
std/digest/sha.d
Examples
//Template API
import std.digest.sha;
ubyte[20] hash1 = sha1Of("abc");
assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
ubyte[28] hash224 = sha224Of("abc");
assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
//Feeding data
ubyte[1024] data;
SHA1 sha1;
sha1.start();
sha1.put(data[]);
sha1.start(); //Start again
sha1.put(data[]);
hash1 = sha1.finish();
//OOP API
import std.digest.sha;
auto sha1 = new SHA1Digest();
ubyte[] hash1 = sha1.digest("abc");
assert(toHexString(hash1) == "A9993E364706816ABA3E25717850C26C9CD0D89D");
auto sha224 = new SHA224Digest();
ubyte[] hash224 = sha224.digest("abc");
assert(toHexString(hash224) == "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7");
//Feeding data
ubyte[1024] data;
sha1.put(data[]);
sha1.reset(); //Start again
sha1.put(data[]);
hash1 = sha1.finish();
sha : (alias template) replay_journal.sha256Of = std.digest.sha.sha256Of(T...)(T data)These are convenience aliases for digest using the
SHA implementation.
sha256Of;
import (package) stdstd.(module) std.digestThis module describes the digest APIs used in Phobos. All digests follow
these APIs. Additionally, this module contains useful helper methods which
can be used with every digest type.
Category Functions
| Template API | isDigest DigestType hasPeek hasBlockSize ExampleDigest digest hexDigest makeDigest |
| OOP API | Digest |
| Helper functions | toHexString secureEqual |
| Implementation helpers | digestLength WrapperDigest |
APIs
There are two APIs for digests: The template API and the OOP API. The template API uses structs
and template helpers like isDigest. The OOP API implements digests as classes inheriting
the Digest interface. All digests are named so that the template API struct is called "x"
and the OOP API class is called "xDigest". For example we have MD5 <--> MD5Digest,
CRC32 <--> CRC32Digest, etc.
The template API is slightly more efficient. It does not have to allocate memory dynamically,
all memory is allocated on the stack. The OOP API has to allocate in the finish method if no
buffer was provided. If you provide a buffer to the OOP APIs finish function, it doesn't allocate,
but the Digest classes still have to be created using new which allocates them using the GC.
The OOP API is useful to change the digest function and/or digest backend at 'runtime'. The benefit here
is that switching e.g. Phobos MD5Digest and an OpenSSLMD5Digest implementation is ABI compatible.
If just one specific digest type and backend is needed, the template API is usually a good fit.
In this simplest case, the template API can even be used without templates: Just use the "x" structs
directly.
Source
std/digest/package.d
CTFE
Digests do not work in CTFE
TODO
Digesting single bits (as opposed to bytes) is not implemented. This will be done as another
template constraint helper (hasBitDigesting!T) and an additional interface (BitDigest)
Examples
import std.digest.crc;
//Simple example
char[8] hexHash = hexDigest!CRC32("The quick brown fox jumps over the lazy dog");
assert(hexHash == "39A34F41");
//Simple example, using the API manually
CRC32 context = makeDigest!CRC32();
context.put(cast(ubyte[])"The quick brown fox jumps over the lazy dog");
ubyte[4] hash = context.finish();
assert(toHexString(hash) == "39A34F41");
//Generating the hashes of a file, idiomatic D way
import std.digest.crc, std.digest.md, std.digest.sha;
import std.stdio;
// Digests a file and prints the result.
void digestFile(Hash)(string filename)
if (isDigest!Hash)
{
auto file = File(filename);
auto result = digest!Hash(file.byChunk(4096 * 1024));
writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result));
}
void main(string[] args)
{
foreach (name; args[1 .. $])
{
digestFile!MD5(name);
digestFile!SHA1(name);
digestFile!CRC32(name);
}
}
//Generating the hashes of a file using the template API
import std.digest.crc, std.digest.md, std.digest.sha;
import std.stdio;
// Digests a file and prints the result.
void digestFile(Hash)(ref Hash hash, string filename)
if (isDigest!Hash)
{
File file = File(filename);
//As digests imlement OutputRange, we could use std.algorithm.copy
//Let's do it manually for now
foreach (buffer; file.byChunk(4096 * 1024))
hash.put(buffer);
auto result = hash.finish();
writefln("%s (%s) = %s", Hash.stringof, filename, toHexString(result));
}
void uMain(string[] args)
{
MD5 md5;
SHA1 sha1;
CRC32 crc32;
md5.start();
sha1.start();
crc32.start();
foreach (arg; args[1 .. $])
{
digestFile(md5, arg);
digestFile(sha1, arg);
digestFile(crc32, arg);
}
}
import std.digest.crc, std.digest.md, std.digest.sha;
import std.stdio;
// Digests a file and prints the result.
void digestFile(Digest hash, string filename)
{
File file = File(filename);
//As digests implement OutputRange, we could use std.algorithm.copy
//Let's do it manually for now
foreach (buffer; file.byChunk(4096 * 1024))
hash.put(buffer);
ubyte[] result = hash.finish();
writefln("%s (%s) = %s", typeid(hash).toString(), filename, toHexString(result));
}
void umain(string[] args)
{
auto md5 = new MD5Digest();
auto sha1 = new SHA1Digest();
auto crc32 = new CRC32Digest();
foreach (arg; args[1 .. $])
{
digestFile(md5, arg);
digestFile(sha1, arg);
digestFile(crc32, arg);
}
}
digest : (alias template) replay_journal.toHexString = std.digest.toHexString(Order order = Order.increasing, ulong num, LetterCase letterCase = LetterCase.upper)(const ubyte[num] digest)Used to convert a hash value (a static or dynamic array of ubytes) to a string.
Can be used with the OOP and with the template API.
The additional order parameter can be used to specify the order of the input data.
By default the data is processed in increasing order, starting at index 0. To process it in the
opposite order, pass Order.decreasing as a parameter.
The additional letterCase parameter can be used to specify the case of the output data.
By default the output is in upper case. To change it to the lower case
pass LetterCase.lower as a parameter.
Note
The function overloads returning a string allocate their return values
using the GC. The versions returning static arrays use pass-by-value for
the return value, effectively avoiding dynamic allocation.
toHexString;
import (package) stdstd.(module) std.exceptionThis module defines functions related to exceptions and general error
handling. It also defines functions intended to aid in unit testing.
Category Functions Assumptions assertNotThrown assertThrown assumeUnique assumeWontThrow mayPointTo Enforce doesPointTo enforce errnoEnforce Handlers collectException collectExceptionMsg ifThrown handle Other basicExceptionCtors emptyExceptionMsg ErrnoException RangePrimitive
Source
std/exception.d
Examples
Synopis
import core.stdc.stdlib : malloc, free;
import std.algorithm.comparison : equal;
import std.algorithm.iteration : map, splitter;
import std.algorithm.searching : endsWith;
import std.conv : ConvException, to;
import std.range : front, retro;
// use enforce like assert
int a = 3;
enforce(a > 2, "a needs to be higher than 2.");
// enforce can throw a custom exception
enforce!ConvException(a > 2, "a needs to be higher than 2.");
// enforce will return it's input
enum size = 42;
auto memory = enforce(malloc(size), "malloc failed")[0 .. size];
scope(exit) free(memory.ptr);
// collectException can be used to test for exceptions
Exception e = collectException("abc".to!int);
assert(e.file.endsWith("conv.d"));
// and just for the exception message
string msg = collectExceptionMsg("abc".to!int);
assert(msg == "Unexpected 'a' when converting from type string to type int");
// assertThrown can be used to assert that an exception is thrown
assertThrown!ConvException("abc".to!int);
// ifThrown can be used to provide a default value if an exception is thrown
assert("x".to!int().ifThrown(0) == 0);
// handle is a more advanced version of ifThrown for ranges
auto r = "12,1337z32,54".splitter(',').map!(a => to!int(a));
auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => 0);
assert(h.equal([12, 0, 54]));
assertThrown!ConvException(h.retro.equal([54, 0, 12]));
// basicExceptionCtors avoids the boilerplate when creating custom exceptions
static class MeaCulpa : Exception
{
mixin basicExceptionCtors;
}
e = collectException((){throw new MeaCulpa("diagnostic message");}());
assert(e.msg == "diagnostic message");
assert(e.file == __FILE__);
assert(e.line == __LINE__ - 3);
// assumeWontThrow can be used to cast throwing code into `nothrow`
void exceptionFreeCode() nothrow
{
// auto-decoding only throws if an invalid UTF char is given
assumeWontThrow("abc".front);
}
// assumeUnique can be used to cast mutable instance to an `immutable` one
// use with care
char[] str = " mutable".dup;
str[0 .. 2] = "im";
immutable res = assumeUnique(str);
assert(res == "immutable");
exception : (alias template) replay_journal.enforce = std.exception.enforce(E : Throwable = Exception) if (is(typeof(new E("", string.init, size_t.init)) : Throwable) || is(typeof(new E(string.init, size_t.init)) : Throwable))Enforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) replay_journal.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) replay_journal.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
import (package) stdstd.(module) std.stringString handling functions.
Category Functions Searching column indexOf indexOfAny indexOfNeither lastIndexOf lastIndexOfAny lastIndexOfNeither Comparison isNumeric Mutation capitalize Pruning and Filling center chomp chompPrefix chop detabber detab entab entabber leftJustify outdent rightJustify strip stripLeft stripRight wrap Substitution abbrev soundex soundexer succ tr translate Miscellaneous assumeUTF fromStringz lineSplitter representation splitLines toStringz Objects of types string, wstring, and dstring are value types and cannot be mutated element-by-element. For using mutation during building strings, use char[], wchar[], or dchar[]. The xxxstring types are preferable because they don't exhibit undesired aliasing, thus making code more robust.
The following functions are publicly imported:
Module Functions Publicly imported functions std.algorithm cmp, std,algorithm,comparison count, std,algorithm,searching endsWith, std,algorithm,searching startsWith, std,algorithm,searching std.array join, std,array replace, std,array replaceInPlace, std,array split, std,array empty, std,array std.format format, std,format sformat, std,format std.uni icmp, std,uni toLower, std,uni toLowerInPlace, std,uni toUpper, std,uni toUpperInPlace, std,uni There is a rich set of functions for string handling defined in other modules. Functions related to Unicode and ASCII are found in std.uni and std.ascii, respectively. Other functions that have a wider generality than just strings can be found in std.algorithm and std.range.
Source
std/string.d
string : (alias template) replay_journal.format = std.format.format(Char, Args...)(in Char[] fmt, Args args) if (isSomeChar!Char)Converts its arguments according to a format string into a string.
The second version of format takes the format string as template
argument. In this case, it is checked for consistency at
compile-time and produces slightly faster code, because the length of
the output buffer can be estimated in advance.
format;
// ── the journal ─────────────────────────────────────────────────────────────
/// One completed step. `fingerprint` is what makes replay detect a changed
/// program: the same key with different arguments is a different step.
struct (struct) replay_journal.EntryOne completed step. fingerprint is what makes replay detect a changed
program: the same key with different arguments is a different step.
Entry
{
(alias) object.string = stringstring (field) string replay_journal.Entry.keykey;
(alias) object.string = stringstring (field) string replay_journal.Entry.fingerprintfingerprint;
(alias) object.string = stringstring (field) string replay_journal.Entry.resultresult;
}
/// A `key` plus `args` fingerprint — the identity a step is matched on.
(alias) object.string = stringstring string replay_journal.fingerprintOf(string key, string args) @safeA key plus args fingerprint — the identity a step is matched on.
fingerprintOf((alias) object.string = stringstring (parameter) string keykey, (alias) object.string = stringstring (parameter) string argsargs) @safe
=> ubyte[32] std.digest.sha.sha256Of!string(string __param_0) pure nothrow @nogc @safeThese are convenience aliases for digest using the
SHA implementation.
sha256Of((parameter) string keykey ~ "\0" ~ (parameter) string argsargs).char[64] std.digest.toHexString!(Order.increasing, 32LU, LetterCase.upper)(const(ubyte[32]) digest) pure nothrow @nogc @safeUsed to convert a hash value (a static or dynamic array of ubytes) to a string.
Can be used with the OOP and with the template API.
The additional order parameter can be used to specify the order of the input data.
By default the data is processed in increasing order, starting at index 0. To process it in the
opposite order, pass Order.decreasing as a parameter.
The additional letterCase parameter can be used to specify the case of the output data.
By default the output is in upper case. To change it to the lower case
pass LetterCase.lower as a parameter.
Note
The function overloads returning a string allocate their return values
using the GC. The versions returning static arrays use pass-by-value for
the return value, effectively avoiding dynamic allocation.
toHexString[0 .. 16].string object.idup!char(char[] a) pure nothrow @property @safeProvide the .idup array property, which creates an immutable duplicate.
idup;
/// Raised when the program diverges from its journal.
class (class) replay_journal.DivergenceRaised when the program diverges from its journal.
Divergence : (class) object.ExceptionThe base class of all errors that are safe to catch and handle.
In principle, only thrown objects derived from this class are safe to catch
inside a catch block. Thrown objects not derived from Exception
represent runtime errors that should not be caught, as certain runtime
guarantees may not hold, making it unsafe to continue program execution.
Examples
bool gotCaught;
try
{
throw new Exception("msg");
}
catch (Exception e)
{
gotCaught = true;
assert(e.msg == "msg");
}
assert(gotCaught);
Exception
{
this(replay_journal.Divergence replay_journal.Divergence.this(string msg) @safestring (parameter) string msgmsg) @safe { super((parameter) string msgmsg); }
}
// ── the handler ─────────────────────────────────────────────────────────────
/// Answers steps from the journal while it lasts, then executes them live.
struct (struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable
{
(struct) replay_journal.EntryOne completed step. fingerprint is what makes replay detect a changed
program: the same key with different arguments is a different step.
Entry[] (field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal; /// the entries recorded so far (input: the prior run's)
(alias) object.size_t = ulongsize_t (field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor; /// how many of them replay has consumed
(alias) object.size_t = ulongsize_t (field) ulong replay_journal.Durable.executedsteps that actually ran in this invocation
executed; /// steps that actually ran in this invocation
/// Runs (or replays) one step. `live` is only invoked when the journal has
/// no entry for this position.
(alias) object.string = stringstring string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step((alias) object.string = stringstring (parameter) string keykey, (alias) object.string = stringstring (parameter) string argsargs, scope (alias) object.string = stringstring delegate() (parameter) string delegate() livelive)
{
const (local variable) const(string) fpfp = string replay_journal.fingerprintOf(string key, string args) @safeA key plus args fingerprint — the identity a step is matched on.
fingerprintOf((parameter) string keykey, (parameter) string argsargs);
if ((field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor < (field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.(field) ulong replay_journal.Entry[].lengthlength)
{
const (local variable) const(replay_journal.Entry) recordedrecorded = (field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal[(field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor];
if ((local variable) const(replay_journal.Entry) recordedrecorded.(field) string replay_journal.Entry.keykey != (parameter) string keykey || (local variable) const(replay_journal.Entry) recordedrecorded.(field) string replay_journal.Entry.fingerprintfingerprint != (local variable) const(string) fpfp)
throw new (class) replay_journal.DivergenceRaised when the program diverges from its journal.
Divergence(string std.format.format!("journal entry %d is `%s` (%s) but the program asked for `%s` (%s)", ulong, string, string, string, string)(ulong __param_0, string __param_1, string __param_2, string __param_3, string __param_4) pure @safeExamples
The format string can be checked at compile-time:
auto s = format!"%s is %s"("Pi", 3.14);
assert(s == "Pi is 3.14");
// This line doesn't compile, because 3.14 cannot be formatted with %d:
// s = format!"%s is %d"("Pi", 3.14);
format!"journal entry %d is `%s` (%s) but the program asked for `%s` (%s)"(
(field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor, (local variable) const(replay_journal.Entry) recordedrecorded.(field) string replay_journal.Entry.keykey, (local variable) const(replay_journal.Entry) recordedrecorded.(field) string replay_journal.Entry.fingerprintfingerprint, (parameter) string keykey, (local variable) const(string) fpfp));
(field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor++;
return (local variable) const(replay_journal.Entry) recordedrecorded.(field) string replay_journal.Entry.resultresult;
}
const (local variable) const(string) resultresult = (parameter) string delegate() livelive();
(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal ~= (struct) replay_journal.EntryOne completed step. fingerprint is what makes replay detect a changed
program: the same key with different arguments is a different step.
Entry((parameter) string keykey, (local variable) const(string) fpfp, (local variable) const(string) resultresult);
(field) ulong replay_journal.Durable.cursorhow many of them replay has consumed
cursor++;
(field) ulong replay_journal.Durable.executedsteps that actually ran in this invocation
executed++;
return (local variable) const(string) resultresult;
}
}
// ── the world ───────────────────────────────────────────────────────────────
/// The external state the workflow acts on. Each effect leaves a trace, so a
/// re-executed step is visible.
struct (struct) replay_journal.WorldThe external state the workflow acts on. Each effect leaves a trace, so a
re-executed step is visible.
World
{
(alias) object.string = stringstring[] (field) string[] replay_journal.World.tagstags;
(alias) object.string = stringstring[] (field) string[] replay_journal.World.pushedpushed;
(alias) object.string = stringstring[] (field) string[] replay_journal.World.logevery effect that actually happened, in order
log; /// every effect that actually happened, in order
}
// ── the workflow ────────────────────────────────────────────────────────────
/// A miniature release: read the range, decide a version, write notes, tag,
/// push. Written once; the same function serves the first run and every
/// resume.
void void replay_journal.releaseWorkflow(ref replay_journal.Durable d, ref replay_journal.World w, string head)A miniature release: read the range, decide a version, write notes, tag,
push. Written once; the same function serves the first run and every
resume.
releaseWorkflow(ref (struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable (parameter) replay_journal.Durable dd, ref (struct) replay_journal.WorldThe external state the workflow acts on. Each effect leaves a trace, so a
re-executed step is visible.
World (parameter) replay_journal.World ww, (alias) object.string = stringstring (parameter) string headhead)
{
const (local variable) const(string) rangerange = (parameter) replay_journal.Durable dd.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("range", (parameter) string headhead, {
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log ~= "git log " ~ (parameter) string headhead;
return "3 commits";
});
const (local variable) const(string) version_version_ = (parameter) replay_journal.Durable dd.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("decide.version", (local variable) const(string) rangerange, {
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log ~= "bump";
return "v0.2.0";
});
const (local variable) const(string) notesnotes = (parameter) replay_journal.Durable dd.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("notes", (local variable) const(string) version_version_, {
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log ~= "agent notes for " ~ (local variable) const(string) version_version_;
return "notes for " ~ (local variable) const(string) version_version_;
});
(parameter) replay_journal.Durable dd.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("tag." ~ (local variable) const(string) version_version_, (local variable) const(string) notesnotes, {
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log ~= "git tag " ~ (local variable) const(string) version_version_;
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.tagstags ~= (local variable) const(string) version_version_;
return "tagged";
});
(parameter) replay_journal.Durable dd.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("push." ~ (local variable) const(string) version_version_, "origin", {
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log ~= "git push " ~ (local variable) const(string) version_version_;
(parameter) replay_journal.World ww.(field) string[] replay_journal.World.pushedpushed ~= (local variable) const(string) version_version_;
return "pushed";
});
}
// ── the demonstration ───────────────────────────────────────────────────────
int int D main()main()
{
// 1. An uninterrupted run: every step executes, the journal fills.
(struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable (local variable) replay_journal.Durable fullfull;
(struct) replay_journal.WorldThe external state the workflow acts on. Each effect leaves a trace, so a
re-executed step is visible.
World (local variable) replay_journal.World w0w0;
void replay_journal.releaseWorkflow(ref replay_journal.Durable d, ref replay_journal.World w, string head)A miniature release: read the range, decide a version, write notes, tag,
push. Written once; the same function serves the first run and every
resume.
releaseWorkflow((local variable) replay_journal.Durable fullfull, (local variable) replay_journal.World w0w0, "abc123");
void std.stdio.writefln!(char, ulong, ulong)(in char[] fmt, ulong __param_1, ulong __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("uninterrupted: %d steps executed, journal has %d entries",
(local variable) replay_journal.Durable fullfull.(field) ulong replay_journal.Durable.executedsteps that actually ran in this invocation
executed, (local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.(field) ulong replay_journal.Entry[].lengthlength);
foreach ((parameter) ulong ii, (parameter) replay_journal.Entry ee; (local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal)
void std.stdio.writefln!(char, ulong, string, string, string)(in char[] fmt, ulong __param_1, string __param_2, string __param_3, string __param_4) @safeEquivalent to writef(fmt, args, '\n').
writefln(" %d %-16s %s %s", (local variable) ulong ii, (local variable) replay_journal.Entry ee.(field) string replay_journal.Entry.keykey, (local variable) replay_journal.Entry ee.(field) string replay_journal.Entry.fingerprintfingerprint, (local variable) replay_journal.Entry ee.(field) string replay_journal.Entry.resultresult);
// 2. Crash-at-every-index: cut after k entries, resume, compare.
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("resume after a crash at each index:");
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln(" cut replayed executed journal identical");
foreach ((local variable) ulong kk; 0 .. (local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.(field) ulong replay_journal.Entry[].lengthlength + 1)
{
(struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable (local variable) replay_journal.Durable resumedresumed = (struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable((local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal[0 .. (local variable) ulong kk].replay_journal.Entry[] object.dup!(replay_journal.Entry)(const(replay_journal.Entry)[] a) pure nothrow @property @safedup);
(struct) replay_journal.WorldThe external state the workflow acts on. Each effect leaves a trace, so a
re-executed step is visible.
World (local variable) replay_journal.World ww;
void replay_journal.releaseWorkflow(ref replay_journal.Durable d, ref replay_journal.World w, string head)A miniature release: read the range, decide a version, write notes, tag,
push. Written once; the same function serves the first run and every
resume.
releaseWorkflow((local variable) replay_journal.Durable resumedresumed, (local variable) replay_journal.World ww, "abc123");
const (local variable) const(bool) identicalidentical = (local variable) replay_journal.Durable resumedresumed.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.bool std.algorithm.comparison.equal!().equal!(replay_journal.Entry[], replay_journal.Entry[])(replay_journal.Entry[] __param_0, replay_journal.Entry[] __param_1) pure nothrow @nogc @safeCompares two or more ranges for equality, as defined by predicate pred
(which is == by default).
Compares two or more ranges for equality. The ranges may have
different element types, as long as all are comparable by means of
the pred.
Performs O(min(rs[0].length, rs[1].length, ...)) evaluations of pred. However, if
equal is invoked with the default predicate, the implementation may take the liberty
to use faster implementations that have the theoretical worst-case
O(max(rs[0].length, rs[1].length, ...)).
At least one of the ranges must be finite. If one range involved is infinite, the result is
(statically known to be) false.
If the ranges have different kinds of UTF code unit (char, wchar, or
dchar), then they are compared using UTF decoding to avoid
accidentally integer-promoting units.
Examples
import std.algorithm.comparison : equal;
import std.math.operations : isClose;
int[4] a = [ 1, 2, 4, 3 ];
assert(!equal(a[], a[1..$]));
assert(equal(a[], a[]));
assert(equal!((a, b) => a == b)(a[], a[]));
// different types
double[4] b = [ 1.0, 2, 4, 3];
assert(!equal(a[], b[1..$]));
assert(equal(a[], b[]));
// predicated: ensure that two vectors are approximately equal
double[4] c = [ 1.0000000005, 2, 4, 3];
assert(equal!isClose(b[], c[]));
Tip
equal can itself be used as a predicate to other functions.
This can be very useful when the element type of a range is itself a
range. In particular, equal can be its own predicate, allowing
range of range (of range...) comparisons.
import std.algorithm.comparison : equal;
import std.range : iota, chunks;
assert(equal!(equal!equal)(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
iota(0, 8).chunks(2).chunks(2)
));
equal((local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal);
const(bool) std.exception.enforce!().enforce!(const(bool))(const(bool) value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((local variable) const(bool) identicalidentical, "resume diverged from the uninterrupted run");
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((local variable) replay_journal.Durable resumedresumed.(field) ulong replay_journal.Durable.executedsteps that actually ran in this invocation
executed == (local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.(field) ulong replay_journal.Entry[].lengthlength - (local variable) ulong kk,
"resume re-executed a journaled step");
void std.stdio.writefln!(char, ulong, ulong, ulong, string)(in char[] fmt, ulong __param_1, ulong __param_2, ulong __param_3, string __param_4) @safeEquivalent to writef(fmt, args, '\n').
writefln(" %3d %8d %8d %s", (local variable) ulong kk, (local variable) ulong kk, (local variable) replay_journal.Durable resumedresumed.(field) ulong replay_journal.Durable.executedsteps that actually ran in this invocation
executed, (local variable) const(bool) identicalidentical ? "yes" : "NO");
}
// 3. Divergence: the same journal, but the program now asks for a different
// step at position 1 (a changed bump policy). Replay refuses.
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
(struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable (local variable) replay_journal.Durable drifteddrifted = (struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable((local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal.replay_journal.Entry[] object.dup!(replay_journal.Entry)(const(replay_journal.Entry)[] a) pure nothrow @property @safedup);
(alias) object.string = stringstring string replay_journal.main.mustNotRun() pure nothrow @nogc @safemustNotRun() { assert(false, "a journaled step must not execute"); }
try
{
(local variable) replay_journal.Durable drifteddrifted.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("range", "abc123", &string replay_journal.main.mustNotRun() pure nothrow @nogc @safemustNotRun);
(local variable) replay_journal.Durable drifteddrifted.string replay_journal.Durable.step(string key, string args, scope string delegate() live)Runs (or replays) one step. live is only invoked when the journal has
no entry for this position.
step("decide.version", "4 commits", &string replay_journal.main.mustNotRun() pure nothrow @nogc @safemustNotRun);
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("divergence NOT detected");
return 1;
}
catch ((class) replay_journal.DivergenceRaised when the program diverges from its journal.
Divergence (local variable) replay_journal.Divergence ee)
void std.stdio.writeln!(string, string)(string __param_0, string __param_1) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("divergence detected: ", (local variable) replay_journal.Divergence ee.(field) string object.Throwable.msgA message describing the error.
msg);
// 4. The world after a resume shows only the steps that were re-executed:
// a crash after the tag re-runs only the push.
(struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable (local variable) replay_journal.Durable afterTagafterTag = (struct) replay_journal.DurableAnswers steps from the journal while it lasts, then executes them live.
Durable((local variable) replay_journal.Durable fullfull.(field) replay_journal.Entry[] replay_journal.Durable.journalthe entries recorded so far (input: the prior run's)
journal[0 .. 4].replay_journal.Entry[] object.dup!(replay_journal.Entry)(const(replay_journal.Entry)[] a) pure nothrow @property @safedup);
(struct) replay_journal.WorldThe external state the workflow acts on. Each effect leaves a trace, so a
re-executed step is visible.
World (local variable) replay_journal.World w4w4;
void replay_journal.releaseWorkflow(ref replay_journal.Durable d, ref replay_journal.World w, string head)A miniature release: read the range, decide a version, write notes, tag,
push. Written once; the same function serves the first run and every
resume.
releaseWorkflow((local variable) replay_journal.Durable afterTagafterTag, (local variable) replay_journal.World w4w4, "abc123");
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writeln!(string, string[])(string __param_0, string[] __param_1) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("effects performed when resuming after the tag: ", (local variable) replay_journal.World w4w4.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log);
bool std.exception.enforce!().enforce!bool(bool value, lazy const(char)[] msg = null, string file = __FILE__, ulong line = cast(ulong)__LINE__) pure @safeEnforces that the given value is true.
If the given value is false, an exception is thrown.
The
msg - error message as a string
dg - custom delegate that return a string and is only called if an exception occurred
ex - custom exception to be thrown. It is lazy and is only created if an exception occurred
enforce((local variable) replay_journal.World w4w4.(field) string[] replay_journal.World.logevery effect that actually happened, in order
log == ["git push v0.2.0"]);
return 0;
}