-3

Many years ago in the old forum I asked Primozh if Pipeline pattern can be kind of Uroboros, feeding half-complete results back to itself.

Back then Primozh said it would be straight-forward and PipeLine stage can feed OmniValues not only to the OUTPUT but to INPUT too.

The problem is that initial feeding stages are run way too fast, they expire and seal the INPUT collection and there is no way to UN-SEAL it, and thus as soon as they try to feed half-baked packets back to themselves - voila! - OTL throws "cannot add to completed collection" exception.

So how can this self-exploding task by the link above be implemented via self-feeding Pipeline pattern?

UPD: Changed the example from "self-exploding" - generating huge amounts of intermediate half-calculated results - permutations generation, to a simple (i hope) calculating of factorial. This however has disadvantage of determinism: it always generate ONE intermediate job item, so the ability of pipeline to deal with growing collections was not tasted.

{$A+} // not $A8
type FactTask = record
  Goal, Curr: Cardinal;
  Value : Int64;
end;

procedure TForm6.Button1Click(Sender: TObject);
var Msg: string;
    f: FactTask;
    Results: TArray<Int64>;
    pipeOut: IOmniBlockingCollection;
    pipe:    IOmniPipeline;
begin
  lblResults.Caption := ' WAIT, we are producing...';
  Repaint;

  pipe := Parallel.Pipeline;
  f.Goal := edLen.Value; // 10
  f.Curr := 0;
  f.Value := 1;

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     begin
       output.Add( TOmniValue.FromRecord( f ) );
     end
  );

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     var f_in, f_out: FactTask; v: TOmniValue;
     begin
       for v in input do begin
         f_in := v.ToRecord<FactTask>;
         if f_in.Curr < f_in.Goal then begin
            f_out.Goal := f_in.Goal;
            f_out.Curr := Succ(f_in.Curr);
            f_out.Value := f_in.Value * f_out.Curr;
            input.Add( TOmniValue.FromRecord( f_out ) ); //  <<< Exception!
         end;
       end;
     end
  );

  pipe.Stage(
     procedure ( const input, output: IOmniBlockingCollection )
     var f_in: FactTask;  v: TOmniValue;
     begin
       for v in input do begin
         f_in := v.ToRecord<FactTask>;
         if f_in.Curr = f_in.Goal then begin
            Output.Add( f_in.Value );
         end;
       end;
     end
  );

  pipe.Run;
  pipeOut := pipe.Output;
//    pipe.WaitFor(INFINITE);  ToArray would efficiently do that
//    pipeOut.CompleteAdding;    ...without frozing on Pipeline/Collections SetThrottle
  Results := TOmniBlockingCollection.ToArray<Int64>(pipeOut);

  Msg := IntToStr(f.Goal) + '! = ' + IntToStr(Results[0]);
  lblResults.Caption := Msg;
  ShowMessage(Msg);
end;

It crashes with the pipeline stage trying to re-fill the input that unexpectedly got sealed by TOmniPipeline.Run. At the marked line the "Cannot add to competed collection" exception is unexpectedly thrown.

How to keep the Pipeline running when collection is balancing between empty and few ( it is not only the starting condition, it would be repeated near the calculations end )?

A bit of dreaming: https://plus.google.com/+AriochThe/posts/LCHnSCmZYtx
Bit more: https://github.com/gabr42/OmniThreadLibrary/issues/61

Arioch 'The
  • 15,799
  • 35
  • 62
  • Did it blocked the zip file - of the URL? If anything, ZIP is told to be downloaded 3 times now. Indeed, any free file exchange service might contain pirated content viruses and what not. Be it rghost of deposit-files or mega-upload or anything. So AV software can rightfully complain that users generated files might contain something bad, though it is not helping... Of course I can convert zip to base64 and put it here as text, but won't it be abusing of the service? – Arioch 'The Jan 24 '16 at 10:04
  • So it is Mozilla blocking it, Just reinforces my assumption, it is not archive blocked for its content, but the free files exchange services are blocked bevause anyone can upload any content including harmful one there. Well, then get it from PasteBin... Or d/l with some other browser. – Arioch 'The Jan 24 '16 at 10:48
  • Don't attach giant binary zips on Stackoverflow. This is a place for people to share questions about Programming not to ask "please debug my code". A question which requires a giant zip of code or even a wall of pages of code posted right here, is not a good question for Stackoverflow. Your title and your question have no meaning in plain english "input sealed" has a meaning known to you alone, and your program basically just doesn't work. Debug it yourself. I suspect you need to fix your design. – Warren P Jan 24 '16 at 15:45
  • There is no "giant zip" and there is no "binary zip", don't know where you pulled them out. Input is a name of a container parameter in OTL Pipeline pattern, if you are oblivious to it then it surely makes but sense to you, as with ANY pattern. Sealed here means the state that nothing can be added/appended/put into the container any more. – Arioch 'The Jan 24 '16 at 16:08
  • 1
    Please post a [MCVE]. – gabr Jan 24 '16 at 16:56
  • Okay, when i'm back at desktop i'll make factorial calculation via multi-threading pipeline. It would be no less binary than any other text, but maybe i bit less gigantic. – Arioch 'The Jan 24 '16 at 17:02
  • @gabr i think killing forum was not helpful. Neither SO nor G+ substitute for it. Also i am thinking to open umbrella report on GitHub with a number of subsequent smaller reports. Are you ok with umbrella issies in your bug tracker ? – Arioch 'The Jan 24 '16 at 17:04
  • @Arioch'The Re: github - just go ahead. – gabr Jan 24 '16 at 17:12
  • Here is the factorial, it generates the same exception in OTL. – Arioch 'The Jan 25 '16 at 08:06

1 Answers1

2

Your demo program works as it should.

Your first stage just outputs one record to its output (by the way, you could do that in the main thread by writing to pipe.Input) and then it closes its output pipe.

This in turn causes second stage to shut down. Before for v in input exits, second stage typically tries to write to input (unless you are really lucky with timing). Its input is, however, already closed and Add raises an exception. Calling TryAdd instead of Add will fix that.

I would guess that Pipeline is not really the abstraction you are looking for and that you would be better off by using something else. I would go with normal low-level task wrapping a TOmniBlockingCollection (for stage 2). You would have to create this blocking collection with Create(1) so that it will be aware that it is feeding itself and will unblock automatically. (See Parallel Search in a Tree example from the book for more information.)

gabr
  • 26,580
  • 9
  • 75
  • 141
  • Yes, as you can see in my yesterday example I tried to add initial task from main thread and then the pipeline consisted of one single stage. It changed nothing. And that state did only exited when OTL told it to do by exiting from the loop. And most definitely when the stage was adding to its input it did not exited yet. – Arioch 'The Jan 25 '16 at 09:04
  • Few years ago you told pipeline is flexible enough to have its route paths reused by feeding incomplete items back to queue. Maybe it was about the time that you added stage(collection,collection) to the initial stage(item,item). Now you said it is no more flexible enough. Sad. Why can not it be made flexible again? – Arioch 'The Jan 25 '16 at 09:13
  • Going lo level is always possible,even back to win api, but the point of the library was to hide complexities... – Arioch 'The Jan 25 '16 at 09:16
  • Basically "do not exit stage" suggestions leads to two low level questions: am I the only worker implementing that stage (if not I'd better exit returning Windows thread to the pool for other workers, but that also would need extra synchronization) and what should not-exiting stage do but infinite loop? / Making a chain of ForEach objects ( for calculations more complex than single stage ) is possible, but that seems like re-implementation of Pipeline – Arioch 'The Jan 25 '16 at 09:33
  • Pipeline is flexible enough to feed data back to itself. It is not flexible/smart enough to determine when and how to stop. Stopping is a hard problem. You have to take care of it yourself. – gabr Jan 25 '16 at 10:03
  • Which brings us to square one: how should one mark the collection that it would not be completed by pipeline? While keeping normal stage structure "For value in input " which is required by pipeline to manage workers distribution in threads pool – Arioch 'The Jan 25 '16 at 18:04
  • 1
    If you don't want to mark collection as completed, then don't complete it. As simple as that. There is no way to go back. – gabr Jan 25 '16 at 18:42
  • I repeat it again: I do not mark collection completed. In both the today's demo and in the yesterday's demo you see that I do not complete it.. You see that CompleteAdding code is commented-out, so you know well I do not do it. So if the collection nget completed it is because OTL does it on its own. Yes, there is no way back, that is why I asking how to bail out from Pipeline's Completing it (without circumventing all its functionality and going low-level). – Arioch 'The Jan 25 '16 at 20:41
  • Yes, you do, by exiting the first stage. When a stage exits, its output pipe is closed. – gabr Jan 26 '16 at 07:29
  • No I do not. Even in this example I merely follow pipeline using pattern providing it to manage thread pool and load balancing and merely it. And in my previous example you saw a single stage pipeline getting source collection from main thread, that single stage was adding a dozen of new elements to input before exiting, still the same exception, thus even locking worker thread did not make it. It still acts the same. Indeed, output collection is closed, but it is closed by OTL not by me. – Arioch 'The Jan 26 '16 at 09:10
  • So how we can signal pipeline that this particular collection has more input sources? There are at least two conceptual approaches to do it from library user POV, though I can not assess subtle implementation details. – Arioch 'The Jan 26 '16 at 09:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101671/discussion-between-gabr-and-arioch-the). – gabr Jan 26 '16 at 12:26