Commit graph

30 commits

Author SHA1 Message Date
1ffee39b23 LibraryApplication: tie DispatcherLoop to thread lifecycle
This solution is basically equivalent to the version implemented directly,
but uses the lifecycle-Hooks available through `ThreadHookable`
to structure the code and separate the concerns better.

This largely completes the switch to the new thread-wrapper..

**the old implementation is not referenced anymore**
2023-10-12 20:23:59 +02:00
29b9126c26 Library: test coverage for lifecycle management
Add a complete demonstration for a setup akin to what we use
for the Session thread: a threaded component which manages itself
but also exposes an external interface, which is opened/closed alongside
2023-10-11 22:02:52 +02:00
7b25609896 Library: test coverage for self-managed thread
...extract and improve the tuple-rewriting function
...improve instance tracking test dummy objects
...complete test coverage and verify proper memory handling
2023-10-11 21:06:56 +02:00
f6a6b0b68f Library: allow to bind a member function into self-managed thread
Oh my.
Yet another hideously complex problem and workaround...

Since a week I am like "almost done"
2023-10-11 13:21:08 +02:00
42eba8425a Library: now able to provide a self-managed thread
After quite some detours, with this take I'm finally able to
provide a stringent design to embody all the variants of thread start
encountered in practice in the Lumiera code base.

Especially the *self-managed* thread is now represented as a special-case
of a lifecycle-hook, and can be embodied into a builder front-end,
able to work with any client-provided thread-wrapper subclass.
2023-10-10 21:45:41 +02:00
fad02bd00e Library: extract hook argument adaption
extract into helper function to improve legibility.

This code is rather tricky since on invocation the hook is only provided
but not invoked. Rather, to adapt the argument types, it is wrapped
into a λ for adaptation, which then must be again bound *by value*
into yet another λ, since the Launch configuration builder is comprised
of a chain of captured functors, to be invoked later from the body of the
thread-wrapper object; this indirect procedure is necessary to ensure
all members are initialised *before* the new thread starts
2023-10-10 20:07:35 +02:00
8b3f9e17cd Library: scaffolding to install thread lifecycle hooks
to cover the identified use-cases a wide variety of functors
must be accepted and adapted appropriately. A special twist arises
from the fact that the complete thread-wrapper component stack works
without RTTI; a derived class can not access the thread-wrapper internals
while the policy component to handle those hooks can not directly downcast
to some derived user provided class. But obviously at usage site it
can be expected to access both realms from such a callback.

The solution is to detect the argument type of the given functor
and to build a two step path for a safe static cast.
2023-10-10 19:47:39 +02:00
578af05ebd Library: policy for lifecycle hooks
after some further mulling over the design, it became clear that
a rather loose coupling to the actual usage scenario is preferrable.

Thus, instead of devising a fixed scheme how to reflect the thread state,
rather the usage can directly hook into some points in the thread lifecycle.
So this policy can be reduced to provide additional storage for functon objects.
2023-10-10 12:48:11 +02:00
5f9683ef10 Library: policy for self-managed thread
...after resolving the fundamental design problems,
a policy mix-in can be defined now for a thread that deletes
its own wrapper at the end of the thread-function.

Such a setup would allow for »fire-and-forget« threads, but with
wrapper and ensuring safe allocations. The prominent use case
for such a setup would be the GUI-Thread.
2023-10-10 02:55:23 +02:00
dd2fe7da59 Library: restructure wrapper in accordance to the solution found
So this finally solves the fundamental problem regarding a race on
initialisation of the thread-wrapper; it does *not* solve the same problem
for classes deriving from thread-wrapper, which renders this design questionable
altogether -- but this is another story.

In the end, this initialisation-race is rooted in the very nature of starting a thread;
it seems there are the two design alternatives:
- expose the thread-creation directly to user code (offloading the responsibility)
- offer building blocks which are inherently dangerous
2023-10-09 16:47:56 +02:00
8518cf1fa0 Library: rearrange launch into the base policy
this is a mere rearrangement of code (+lots of comments),
but helps to structure the overall construction better.

ThreadWrapper::launchThread() now does the actual work to build
the active std::thread object and assign it to the thread handle,
while buildLauncher is defined in the context of the constructors
and deals with wiring the functors and decaying/copying of arguments.
2023-10-09 04:13:01 +02:00
2d7137e776 Library: get rid of the invoker-helper
If we package all arguments together into a single tuple,
even including the member-function reference and the this-ptr
for the invokeThreadFunction(), which is the actual thread-functor,
then we can rely on std::make_from_tuple<T>(tuple), which implements
precisely the same hand-over via a std::index_sequence, as used by the
explicitly coded solution -- getting rid of some highly technical boilerplate
2023-10-09 03:19:06 +02:00
faa0d3e211 Library: solved embedding arbitrary argument sequences
Concept study of the intended solution successful.

Can now transparently embed any conceivable functor
and an arbitrary argument sequence into a launcher-λ
Materialising into a std::tuple<decay_t<TYPES...>> did the trick.
2023-10-09 02:57:03 +02:00
fd0370bd11 Library: still fighting to get the design straight
Considering a solution to shift the actual launch of the new thread
from the initialiser list into the ctor body, to circumvent the possible
"undefined behaviour". This would also be prerequisite for defining
a self-managed variant of the thread-wrapper.

Alternative / Plan.B would be to abandon the idea of a self-contained
"thread" building block, instead relying on precise setup in the usage
context -- however, not willing to yield yet, since that would be exactly
what I wanted to avoid: having technicalities of thread start, argument
handover and failure detection intermingled with the business code.
2023-10-08 17:26:36 +02:00
88b91d204c Library: identified further use-case variants to cover
...while reworking the application code, it became clear that
actually there are two further quite distinct variants of usage.
And while these could be implemented with some trickery based on
the Thread-wrapper defined thus far, it seems prudent better to
establish a safely confined explicit setup for these cases:

- a fire-and-forget-thread, which manages its own memory autonomously
- a thread with explicit lifecycle, with detectable not-running state
2023-10-05 23:35:52 +02:00
6cd16a61a6 Library/Application: switch SubsystemRunner_test 2023-10-03 20:49:59 +02:00
d879ae7fbd Library: fix cause of the deadlock in Session-Thread
... which became apparent after switching to the new Thread-wrapper implementation
... the reason is a bug in the Thread-Monitor (which will also be reworked soon)
2023-10-01 20:29:11 +02:00
fdd8e2d595 Library: identify reason for deadlock
- the deadlock was caused by leaking error state through the C-style lumiera_error

- but the reason for the deadlock lies in the »convenience shortcut«
  in the Object-Monitor scope guard for entering a wait state immediately.
  This function undermines the unlocking-guarantee, when an exception
  emanates from within the wait() function itself.
2023-09-30 23:55:42 +02:00
d79e33f797 Library: verify thread self-recognition
...this function was also ported to the new wrapper,
and can be verified now in a much more succinct way.

''This completes porting of the thread-wrapper''
2023-09-30 00:10:09 +02:00
1d625a01e0 Library: complete and modernise ThreadWrapperJoin_test
Since the decision was taken to retain support for this special feature,
and even extend it to allow passing values, the additional functionality
should be documented in the test. Doing so also highlighted subtle problems
with argument binding.
2023-09-29 23:42:22 +02:00
1512e017e1 Library: sharpen argument binding
A subtle yet important point: arguments will always be copied into the new thread.
This is a (very sensible) limitation introduced by the C++ standard.

To support seamless use, the thread-wrapper now rewrites the argument types
picked up from the invocation, to prevent passing on a reference type,
which typically ensues when invoking with a variable name. Otherwise
confusing error messages would be emitted from deep within the STD library.

As a further consequence, function signatures involving reference arguments
can no longer be bound (which is desirable; a function to be performed
within a separate thread must either rely on value arguments, or deliberately
use std::ref wrappers to pass references, assuming you know what you're doing)
2023-09-29 21:36:03 +02:00
201672a0ad Library: reconsider join / stringify API
- it is not directly possible to provide a variadic join(args...),
  due to overload resolution ambiguities

- as a remedy, simplify the invocation of stringify() for the typical cases,
  and provide some frequently used shortcuts
2023-09-29 17:00:13 +02:00
691d2b43fa Library: add shortcut-ctor for own-member function
A common usage pattern is to derive from lib::Thread
and then implement the actual thread function as a member function
of this special-Thread-object (possibly also involving other data members)

Provide a simplified invocation for this special case,
also generating the thread-id automatically from the arguments
2023-09-28 17:45:32 +02:00
2c18c39c18 Library: complete the Thread-joining policy
after all this groundwork, implementing the invocation,
capturing and hand-over of results is simple, and the
thread-wrapper classes became fairly understandable.
2023-09-28 02:09:36 +02:00
284984ad27 Library: thread-wrapper policy based design
Code written thus far was roughly complete and seems to work,
yet structure and life-cycle behaviour was confusing
2023-09-27 18:51:39 +02:00
3fa4f02737 Library: new thread-wrapper implementation complete
- relocate some code into a dedicated translation unit to reduce #includes
- actually set the thread-ID (the old implementation had only a TODO at that point)
2023-09-26 02:32:48 +02:00
67b010ba7e Library: (re)introduce the distinction join / detach
While it would be straight forward from an implementation POV
to just expose both variants on the API (as the C++ standard does),
it seems prudent to enforce the distinction, and to highlight the
auto-detaching behaviour as the preferred standard case.

Creating worker threads just for one computation and joining the results
seemed like a good idea 30 years ago; today we prefer Futures or asynchronous
messaging to achieve similar results in a robust and performant way.

ThreadJoinable can come in handy however for writing unit tests, were
the controlling master thread has to wait prior to perform verification.

So the old design seems well advised in this respect and will be retained
2023-09-26 01:00:00 +02:00
84369201f4 Library: thread-wrapper design and code-organisation 2023-09-25 17:55:50 +02:00
c9a0203492 Library: gut and remould the existing thread-wrapper
- cut the ties to the old POSIX-based custom threadpool framework
- remove operations deemed no longer necessary
- sync() obsoleted by the new SyncBarrier
- support anything std::invoke supports
2023-09-25 16:27:38 +02:00
416895b5b2 Library: prepare switch of Thread-wrapper to C++17
The investigation for #1279 leads to the following conclusions

- the features and the design of our custom thread-wrapper
  almost entirely matches the design chosen meanwhile by the C++ committee

- the implementation provided by the standard library however uses
  modern techniques (especially Atomics) and is more precisely worked out
  than our custom implementation was.

- we do not need an *active* threadpool with work-assignment,
  rather we'll use *active* workers and a *passive* pool,
  which was easy to implement based on C++17 features

==> decision to drop our POSIX based custom implementation
    and to retrofit the Thread-wrapper as a drop-in replacement

+++ start this refactoring by moving code into the Library
+++ create a copy of the Threadwrapper-code to build and test
    the refactorings while the application itself still uses
    existing code, until the transition is complete
2023-09-21 23:23:55 +02:00