diff --git a/doc/technical/howto/crackNuts.txt b/doc/technical/howto/crackNuts.txt
index e66c488ac..d92ac46f2 100644
--- a/doc/technical/howto/crackNuts.txt
+++ b/doc/technical/howto/crackNuts.txt
@@ -88,6 +88,33 @@ test with random numbers::
several threads; each thread should use its own generator then (yet this can be handled sloppy if
the _quality_ of the random number actually does not matter!)
+special test setup::
+ It can be very helpful to use _policy based design_ for some complex evaluation or algorithm,
+ even in cases where it is clear that the actual usage will only ever involve one specific data type
+ and configuration. Not only does such a structured approach help with clarifying the design itself,
+ since the core of the evaluation logic can be abstracted from data representation, but only the
+ testing and verification of correctness is simplified, as the test can be written to use simple,
+ human-readable data (strings, symbols), and, furthermore, many kinds of instrumentation can be
+ included into the test data to gain detailed insight what is happening in the test subject.
+
+verify behaviour sequence::
+ Use the `lib::EventLog`:
++
+- within the test, notable points are marked into the log, possibly with arguments
+- after the test, the Event Log can be verified with a search-sequence
+- this way it is possible to ensure that some events did happen, and happened
+ in a very specific order, or that some event did _not_ happen after another event
+- the `test::Tracker` defined in 'src/test/tracking-dummy.hpp' will mark all constructor,
+ destructor and copy events into an embedded Event Log instance. By inheriting or mix-in
+ from that type, the allocation and instance behaviour of some service can be scrutinised.
+
+verify incidences and concurrency::
+ Aside from using some generic tracing utility, sometimes it is much easier to setup
+ a _specifically tailored instrumentation_. This can be achieved with 'incidence-count.hpp'
++
+- for instrumentation, the `markEnter()` and `markLeave()` functions need to be invoked
+- after the test run, various statistics can be computed and evaluated, including
+ cumulated time and concurrency for the "cases" marked with enter / leave.
Common Tasks
@@ -109,6 +136,12 @@ structured data::
- also useful as output format, both for debugging and because it can be matched against
an expected structure, which is generated with the DSL notation.
- [yellow-background]#(planned)# will be able to map this from/to JSON easily.
+scoped collection::
+ unique-ownership collection that holds a sequence of objects in a preallocated fixed
+ storage block. Notably even non-copyable objects can be managed (which is not possible
+ with a `std::vector`), and type-erased instances of subclasses can be mixed. Furthermore
+ there is a constructor variant that allows to populate a `ScopedCollection` in RAII-style.
+ This can e.g. be used to fire-off a set of Threads or open a set of wrapped connections.
Iterating
~~~~~~~~~
@@ -134,6 +167,26 @@ IterSource::
even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
and application both happen demand-driven or _lazy..._
+State Core::
+ When some algorithm involves a statefull evaluation, it might be helpful to package that into
+ a _State Core_ container. This is a class that is expected to expose three operations, which
+ allow to build an iteration on top. The header 'iter-adapter.hpp' provides several adapters
+ and wrappers, notable `lib::IterStateWrapper` wraps any _state core_ and provides a
+ Lumiera Iterator interface; likewise, `IterExplorer` heavily relies on that concept and
+ is able to accept and iterate any state core in a pipeline.
+special iterators::
+ Some additional adaptors are provided as building block so that specific evaluations
+ can be made to conform with the »Lumiera Iterator« concept
++
+- several given iterators can be combined into a iteration over tuples with 'lib/iter-zip.hpp'
+- there is also a variation 'lib::izip' that adds a running index into the _zipping operation_
+- IterExplorer provides a `flatten()` operation, that can be used to join an iterator-of-iterators
+ into a single seamless iteration lazily.
+- when a STL container also supports _backwards iteration_, then a lumiera iterator can be
+ created that can _switch direction_ dynamically, with the help of 'iter-cursor.hpp'
+- a STL »deque« container can be used both as a stack and a queue; the header 'iter-stack.hpp'
+ provides an iteration adapter for both usage schemes, so that either the stack or the queue
+ can be _"discharged"_ as a Lumiera Iterator (possibly with an IterExplorer pipeline)
Formatting
@@ -155,6 +208,25 @@ Formatting
because there are way more capable solutions _out there_ ☺)
+Setup and wiring
+~~~~~~~~~~~~~~~~
+Generally speaking, we prefer _constructor wiring_ whenever possible, to avoid creating
+unnecessary statefulness (as would be the case when connecting dependencies through setters).
+This design also favours decomposition into self-contained components with a single purpose,
+and simplifies error handling with the help of the RAII pattern.
+
+However, sometimes a piece of code depends on some remote and even global service; in these
+cases, the `lib::Depend` framework can be used to access that service via type name. The
+actual service can either be created on-demand as a singleton, or it can be installed
+explicitly as a service instance. Furthermore, it is possible to "push aside" the
+current service instance temporarily and replace it by a _test mock_.
+
+One point to note is that `lib::Depend` is a _monostate_, and not a _singleton_.
+The actual service should be written as a free-standing class, that can be instantiated
+directly for the purpose of testing.
+
+
+
Language constructs
-------------------
Templates
@@ -240,7 +312,7 @@ index and iterate over tuple elements::
This is another example of bridging the compile-time / runtime barrier, using a trampoline.
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
- need to convert each of the tuple elements into a _common reconciled type.
+ need to convert each of the tuple elements into a _common reconciled type_.
+
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`
diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp
index a42948bb7..3934be30d 100644
--- a/src/lib/scoped-collection.hpp
+++ b/src/lib/scoped-collection.hpp
@@ -2,7 +2,7 @@
SCOPED-COLLECTION.hpp - managing a fixed collection of noncopyable polymorphic objects
Copyright (C)
- 2012, Hermann Vosseler
+ 2012,2026 Hermann Vosseler
**Lumiera** is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
@@ -13,8 +13,8 @@
/** @file scoped-collection.hpp
** Manage a collection of non-copyable polymorphic objects in compact storage.
- ** This helper supports common situation where a service implementation has to
- ** manages a fixed collection of implementation related sub-components, treated
+ ** This helper supports the common situation where a service implementation has
+ ** to manage a fixed collection of implementation related sub-components, treated
** with reference semantics. Typically, those objects are used polymorphically,
** and often even with _type erasure_ and added step by step through a builder.
** The storage to hold all those child objects is allocated in one chunk
@@ -31,7 +31,7 @@
** the managing scope is alive and able to access them.
**
** There are two different usage patterns for populating a ScopedCollection
- ** - the "stack style" usage creates an empty container (using the one arg
+ ** - the **stack style** usage creates an empty container (using the one arg
** ctor to specify the maximum size only). The storage capacity to hold
** a number of objects, up to the predefined maximum, is (heap) allocated
** immediately at construction of the container, yet no child objects are
@@ -39,11 +39,11 @@
** by invoking #emplaceElement() to create a new element of the default
** type `I`) or #emplace(args) to create some subtype. This way,
** the container is gradually filled up.
- ** - the "RAII style" usage strives to create all of the content objects
- ** right away, immediately after the memory allocation. This usage pattern
- ** avoids any kind of "lifecycle state". Either the container comes up sane
- ** and fully populated, or the ctor call fails and any already created
- ** objects are discarded.
+ ** - the **RAII-style** usage attempts to create all the contained objects
+ ** right away, immediately after memory allocation. This usage pattern
+ ** avoids any kind of _lifecycle state_. Either the container is in sane
+ ** state and fully populated from the outset, or the call to the ctor fails
+ ** and all objects that have already been created are discarded.
** @note intentionally there is no operation to discard individual objects,
** the only option is to #clear() the whole container.
** @note the container can hold instances of a subclass of the type defined
@@ -69,7 +69,7 @@
#include "lib/iter-adapter.hpp"
#include
-#include
+#include
namespace lib {
@@ -78,6 +78,7 @@ namespace lib {
using LERR_(INDEX_BOUNDS);
using LERR_(CAPACITY);
+ using meta::lum_iter;
/**
@@ -100,8 +101,8 @@ namespace lib {
public:
/**
* Storage Frame to hold one Child object.
- * The storage will be an heap allocated
- * array of such Wrapper objects.
+ * The storage will be an heap allocated array
+ * of such Wrapper objects.
* @note doesn't manage the Child
*/
class ElementHolder
@@ -126,7 +127,6 @@ namespace lib {
-
/** place object of type TY, forwarding ctor arguments */
template
TY&
@@ -150,6 +150,9 @@ namespace lib {
clear();
}
+ /** create an empty ScopedCollection with given max capacity
+ * @remark this ctor variant is intended for _stack-like_ usage
+ */
explicit
ScopedCollection (size_t maxElements)
: level_(0)
@@ -164,7 +167,7 @@ namespace lib {
* It gets an ElementHolder& as parameter, and should
* use this to create an object of suitable I-subclass
*/
- template
+ template CTOR>
ScopedCollection (size_t maxElements, CTOR builder)
: level_(0)
, capacity_(maxElements)
@@ -190,22 +193,44 @@ namespace lib {
populate_by (builder,instance);
}
+ /** variation of RAII-style: "pull" elements from an iterator.
+ * Each element will be created directly by invoking either the copy ctor,
+ * or a suitable 1-argument ctor, depending on the type yielded by the iterator.
+ * No further elements will be created however, once the iterator is exhausted.
+ * @note anything in accordance to the Lumiera Forward concept is acceptable.
+ * This rules out just passing a plain STL iterator (because these can't
+ * tell for themselves when they're exhausted). Use an suitable iter-adapter
+ * instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
+ */
+ template
+ ScopedCollection (size_t maxElements, IT&& iter)
+ : level_(0)
+ , capacity_(maxElements)
+ , elements_(new ElementHolder[maxElements])
+ {
+ pull_from (std::forward (iter));
+ }
+
/* == some pre-defined Builders == */
- class FillAll; ///< fills the ScopedCollection with default constructed I-instances
+ /** Builder: ills the given storage slot with a I-instance
+ * constructed from copies of the given arguments.
+ * @return builder-λ to pass into ScopeCollection's ctor. */
+ template
+ static auto fill (ARGS&&... args);
- template
- class FillWith; ///< fills the ScopedCollection with default constructed TY-instances
+ /** Builder: fills the given storage slot with a TY-instance
+ * constructed from copies of the given arguments. */
+ template
+ static auto fillWith (ARGS&&... args);
- template
- class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT
+ /** Builder: fills the given storage slot from invoking the
+ * provided factory functor, with either an I-element or a
+ * I-subtype, depending on the functor's result type.
+ * @note the functor will be copied in the resulting λ */
+ template FUN>
+ static auto invoke (FUN&& factory);
- template
- static PullFrom
- pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator
- {
- return PullFrom (iter);
- }
void
clear()
@@ -247,7 +272,7 @@ namespace lib {
* ElementHolder#create function, which places a new element
* into the storage frame passed as parameter.
*/
- template
+ template CTOR>
void
populate_by (CTOR builder)
try {
@@ -282,11 +307,46 @@ namespace lib {
catch(...)
{
WARN (progress, "Failure while populating ScopedCollection. "
- "All elements will be discarded");
+ "All elements will be discarded.");
clear();
throw;
}
+ template
+ struct _BuildFromIter ///< @internal helper to determine what to build from an iterator
+ {
+ using Yield = iter::Yield;
+ using Value = meta::ValueTypeBinding::value_type;
+ using Type = std::conditional_t, Value, I>;
+ static_assert (std::is_constructible_v
+ ,"Iterator must produce something "
+ "that can be used to build contents of this container");
+ };
+
+ /** variation of element initialisation,
+ * creating further elements from the results of an iterator,
+ * until either the container is filled up or the iterator exhausted.
+ */
+ template
+ void
+ pull_from (IT iter)
+ try {
+ while (iter and level_ < capacity_)
+ {
+ using Element = _BuildFromIter::Type;
+ ElementHolder& storageFrame (elements_[level_]);
+ storageFrame.template create (*iter);
+ ++level_;
+ ++iter;
+ } }
+ catch(...)
+ {
+ WARN (progress, "Failure while pulling Iterator into ScopedCollection. "
+ "All elements will be discarded.");
+ clear();
+ throw;
+ }
+
/** push a new element of default type
@@ -340,8 +400,8 @@ namespace lib {
size_t size () const { return level_; }
- size_t capacity () const { return capacity_; }
- bool empty () const { return 0 == level_; }
+ size_t capacity() const { return capacity_; }
+ bool empty() const { return 0 == level_; }
// use in standard range for loops...
@@ -354,7 +414,7 @@ namespace lib {
private:
/* ==== Storage: heap allocated array of element buffers ==== */
- typedef std::unique_ptr ElementStorage;
+ using ElementStorage = std::unique_ptr;
size_t level_;
size_t capacity_;
@@ -417,66 +477,66 @@ namespace lib {
/* === Supplement: pre-defined element builders === */
/** \par usage
- * Pass an instance of this builder functor as 2nd parameter
- * to ScopedCollections's ctor. (an anonymous instance is OK).
+ * On invocation, this function generates a builder-λ to pass as
+ * the 2nd parameter to ScopedCollections's ctor. When arguments are given,
+ * these are perfect-forwarded into a copy in the closure, which implies that
+ * all instances will receive either a copy or const-reference to these values.
+ * (It is not possible to pass references this way).
* Using this variant of the ctor switches the collection to RAII-style:
- * It will immediately try to create all the embedded objects, invoking this
+ * It will immediately try to create all the embedded objects, invoking the
* builder functor for each "slot" to hold such an embedded object. Actually,
* this "slot" is an ElementHolder instance, which provides functions for
* placement-creating objects into this embedded buffer.
+ * @note allows also to default-create or copy-create
*/
template
- class ScopedCollection::FillAll
- {
- public:
- void
- operator() (ScopedCollection::ElementHolder& storage)
- {
- storage.template create();
- }
- };
+ template
+ auto
+ ScopedCollection::fill (ARGS&&... args)
+ {
+ return [... args = std::forward (args)]
+ (ScopedCollection::ElementHolder& storage)
+ {
+ storage.template create (args...);
+ };
+ }
template
- template
- class ScopedCollection::FillWith
- {
- public:
- void
- operator() (ScopedCollection::ElementHolder& storage)
- {
- storage.template create();
- }
- };
+ template
+ auto
+ ScopedCollection::fillWith (ARGS&&... args)
+ {
+ return [... args = std::forward (args)]
+ (ScopedCollection::ElementHolder& storage)
+ {
+ storage.template create (args...);
+ };
+ }
- /** \par usage
- * This variant allows to "pull" elements from an iterator.
- * Actually, the collection will try to create each element right away,
- * by invoking the copy ctor and passing the value yielded by the iterator.
- * @note anything in accordance to the Lumiera Forward Iterator pattern is OK.
- * This rules out just passing a plain STL iterator (because these can't
- * tell for themselves when they're exhausted). Use an suitable iter-adapter
- * instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
+ /**
+ * @remark this builder handles the technical aspect of creating elements
+ * into the slots of the ElementHolder storage, so that client code
+ * has only to define the constructor argument, which notably can
+ * also be a subclass of \a I (assuming the storage size suffices)
+ * @return builder-λ that can be passed into ScopeCollection's ctor.
*/
template
- template
- class ScopedCollection::PullFrom
- {
- IT iter_;
-
- using ElementType = meta::ValueTypeBinding::value_type;
-
- public:
- PullFrom (IT source)
- : iter_(source)
- { }
-
- void
- operator() (ScopedCollection::ElementHolder& storage)
- {
- storage.template create (*iter_);
- ++iter_;
- }
- };
+ template FUN>
+ auto
+ ScopedCollection::invoke (FUN&& factory)
+ {
+ using Res = std::invoke_result_t;
+ using Elem = std::conditional_t, Res, I>;
+ static_assert (std::is_constructible_v
+ ,"Iterator must produce something "
+ "that can be used to build contents of this container");
+
+ return [fac = std::forward (factory)]
+ (ScopedCollection::ElementHolder& storage) mutable
+ {
+ storage.template create (fac());
+ };
+ }
diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp
index 107c9102e..7f12c954b 100644
--- a/src/lib/thread.hpp
+++ b/src/lib/thread.hpp
@@ -451,6 +451,8 @@ namespace lib {
* and possibly configure further details, depending on the actual Policy used.
* @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance
* and invokes a chain of λ-functions collected in the member #launch
+ * @note a Launch instance can be built by direct conversion from an arg-less
+ * thread function (because the ctor is _not_ explicit)
*/
struct Launch
: util::MoveOnly
@@ -571,6 +573,10 @@ namespace lib {
* Primary constructor: Launch the new thread with flexible configuration.
* @param launcher a #Launch builder with a λ-chain to configure and
* finally trigger start of the thread
+ * @note The Launch instance can be built by implicit conversion from any
+ * arg-less invocable; this implies that ThreadLifecycle (and its
+ * relevant subclasses) can be built without much ado directly
+ * from a Lambda that defines the thread-function.
*/
ThreadLifecycle (Launch launcher)
: Policy{launcher.id}
@@ -644,6 +650,12 @@ namespace lib {
* thread has already copied the arguments and indeed actively started to run.
* @warning The destructor waits for a short grace period of 20ms, but calls
* `std::terminate` afterwards, should the thread still be active then.
+ * @remark creating a Thread instance immediately starts the thread;
+ * it can be built from
+ * - any argument-less invocable or lambda (that is invoked in the thread)
+ * - a string argument for the thread-id, an invocable, further arguments...
+ * - a member pointer and further arguments; useful for derived classes
+ * - a ThreadLifecycle::Launch, which allows fine grained configuration
*/
class Thread
: public thread::ThreadLifecycle
diff --git a/tests/lib/local-slice-test.cpp b/tests/lib/local-slice-test.cpp
index 36761565c..5cbf901e3 100644
--- a/tests/lib/local-slice-test.cpp
+++ b/tests/lib/local-slice-test.cpp
@@ -129,19 +129,19 @@ namespace test {
// launch a significant number of threads,
// each verifying the same invocation sequence
- lib::ScopedCollection threads{NUM_THREADS};
- for (uint i=1; i<=NUM_THREADS; ++i)
- threads.emplace ([&]{
- CHECK (i1 == myDummy.invoke());
-
- sleep_for (100us);
-
- CHECK (i2 == myDummy.invoke());
-
- sleep_for (100us);
-
- CHECK (i3 == myDummy.invoke());
- });
+ using Threads = lib::ScopedCollection;
+ Threads threads{NUM_THREADS
+ ,Threads::fill ([&]{
+ CHECK (i1 == myDummy.invoke());
+
+ sleep_for (100us);
+
+ CHECK (i2 == myDummy.invoke());
+
+ sleep_for (100us);
+
+ CHECK (i3 == myDummy.invoke());
+ })};
while (explore(threads).has_any())
yield(); // wait for all threads to finish
diff --git a/tests/lib/scoped-collection-test.cpp b/tests/lib/scoped-collection-test.cpp
index 2efade94b..0f9c34a3a 100644
--- a/tests/lib/scoped-collection-test.cpp
+++ b/tests/lib/scoped-collection-test.cpp
@@ -379,9 +379,9 @@ namespace test{
{
CHECK (0 == Dummy::checksum());
- CollD coll (25, CollD::FillAll() );
+ CollD coll (25, CollD::fill() );
- CHECK (!isnil (coll));
+ CHECK (not isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
@@ -393,14 +393,16 @@ namespace test{
}
+ /** @test use a "populator" that creates a subclass and binds arguments */
void
verify_subclassPopulator()
{
CHECK (0 == Dummy::checksum());
- CollD coll (25, CollD::FillWith() );
+ const uint arg{42};
+ CollD coll (25, CollD::fillWith (arg, 55) );
- CHECK (!isnil (coll));
+ CHECK (not isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
@@ -409,29 +411,32 @@ namespace test{
}
+ /** @test verify dynamic pulling of elements
+ * - the first step uses a "populator" that invokes a λ
+ * - the second step then uses the source container's iterator,
+ * which happens to be a _Lumiera Forward Iterator_ to pull
+ * copies into a second container.
+ * - even while the second container is dimensioned to be larger,
+ * only the number of elements yielded by the iterator are created.
+ */
void
verify_iteratorPopulator()
{
typedef ScopedCollection CollI;
- CollI source (25);
- for (uint i=0; i < source.capacity(); ++i)
- source.emplace(i); // holding the numbers 0..24
-
- CollI coll (20, CollI::pull(source.begin()));
- // this immediately pulls in the first 20 elements
- CHECK (!isnil (coll));
+ CollI source (20, CollI::invoke([i=0] mutable { return i++; }));
+ // filled with numbers 0..19
+ CollI coll (30, source.begin()); // this immediately pulls in the first 20 elements
+ CHECK (not isnil (coll));
CHECK (20 == coll.size());
- CHECK (25 == source.size());
+ CHECK (20 == source.size());
+ CHECK (30 == coll.capacity());
for (uint i=0; i < coll.size(); ++i)
{
CHECK (coll[i] == i );
CHECK (coll[i] == source[i]);
}
-
- // note: the iterator is assumed to deliver a sufficient amount of elements
- VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin())));
}
diff --git a/tests/lib/sync-classlock-test.cpp b/tests/lib/sync-classlock-test.cpp
index 138b108ce..26cb8b19b 100644
--- a/tests/lib/sync-classlock-test.cpp
+++ b/tests/lib/sync-classlock-test.cpp
@@ -19,10 +19,12 @@
#include "test/run.hpp"
#include "lib/sync-classlock.hpp"
#include "lib/scoped-collection.hpp"
+#include "lib/iter-explorer.hpp"
#include "lib/thread.hpp"
using test::Test;
-//using vault::ThreadJoinable; //////////////////WIP
+using lib::explore;
+using std::this_thread::yield;
namespace lib {
namespace test {
@@ -58,29 +60,25 @@ namespace test {
auto gen = buildCappedSubSequence(defaultGen);
int contended = 0;
- using Threads = lib::ScopedCollection>;
+ using Threads = lib::ScopedCollection;
- // Start a bunch of threads with random access pattern
Threads threads{NUM_THREADS,
- [&](Threads::ElementHolder& storage)
- {
- storage.create> ("Sync-ClassLock stress test"
- ,[&]{
- for (uint i=0; i guard;
- ++contended;
- }
- }
- });
- }
- };
+ // Start a bunch of threads with random access pattern...
+ Threads::fill ("Sync-ClassLock stress test"
+ ,[&]{
+ for (uint i=0; i guard;
+ ++contended;
+ }
+ }
+ })};
- for (auto& thread : threads)
- thread.join(); // block until thread terminates // @suppress("Return value not evaluated")
+ while (explore(threads).has_any())
+ yield(); // wait for all threads to terminate
CHECK (contended == NUM_THREADS * NUM_LOOP,
"ALARM: Lock failed, concurrent modification "
diff --git a/tests/vault/mem/engine-buffer-manager-test.cpp b/tests/vault/mem/engine-buffer-manager-test.cpp
index c5922f693..b68563451 100644
--- a/tests/vault/mem/engine-buffer-manager-test.cpp
+++ b/tests/vault/mem/engine-buffer-manager-test.cpp
@@ -225,11 +225,9 @@ namespace test {
CHECK (myPool.empty()); // no further pending / duplicate Alloc in-queue
};
- using Threads = lib::ScopedCollection;
- auto startThread = [&](auto& storage){ storage.template create (workSeq); };
-
// Start a collection of workers....
- Threads threads{NUM_THREADS, startThread};
+ using Threads = lib::ScopedCollection;
+ Threads threads{NUM_THREADS, Threads::fill(workSeq)};
// after that barrier, all threads send back their allocation
afterThread.sync();
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index b9d1eab6f..265f4bb0a 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -145915,7 +145915,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -145924,8 +145925,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -145954,7 +145955,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146162,8 +146163,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -146200,8 +146201,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -146212,7 +146213,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -146223,7 +146225,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146367,7 +146369,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146409,8 +146411,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -146431,7 +146433,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -146451,7 +146454,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146473,7 +146476,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146725,8 +146728,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -146938,8 +146941,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
im Grunde müßte man nur den Zugang zum Memory-Manager selber locken, denn die ganzen Queue-Operationen sind ja schon lock-free und threadsafe; auch könnten diese Locks dann non-recursive sein
-
-
+