Hi,
I ran into a similar problem in VAST that I got around by extending Block and Process. I extended Block with #forkReady, #forkReadyAt: and some others and Process with #ready. The #forkReady extension to Block basically does what it says, it creates a new fork or process in the ready state by does not start it immediately. The new process will wait for its normal turn to start. The #ready extension to Process makes the process ready without running it immediately and is used by #forkReady. I didn't want to change the way #fork worked so I made the extensions, but I don't see any reason why #fork couldn't be changed to work the way I made #forkReady work. I would be surprised if anyone was counting on #fork immediately starting the new process before the process that creates the fork would yield for other reasons. Lou On Mon, 04 Feb 2008 13:04:13 -0800, Andreas Raab <[hidden email]> wrote: >Hi - > >In my never-ending quest for questionable behavior in multi-threaded >situations just today I ran into a pattern which is dangerously common >in our code. It basically goes like this: > >MyClass>>startWorkerProcess > "worker is an instance variable" > worker:= [self runWorkerProcess] fork. > >MyClass>>runWorkerProcess > "Run the worker process" > [Processor activeProcess == worker] whileTrue:[ > "...do the work..." > ]. > >MyClass>>stopWorkerProcess > "Stop the worker process" > worker := nil. "let it terminate itself" > >Those of you who can immediately tell what the problem is should get a >medal for an outstanding knack of analyzing concurrency problems ;-) > >For the rest of us, the problem is that #fork in the above is not >deterministic in the way that there is no guarantee whether the "worker" >variable will be assigned when we enter the worker loop. It *would* be >deterministic if the priority were below or above the current process' >priority but when it's the same it can be affected by environmental >effects (external signals, delay processing etc) leading to some very >obscure runtime problems (in the above, the process would just not start). > >To fix this problem I have changed BlockContext>>fork and >BlockContext>>forkAt: to read, e.g., > >BlockContext>>fork > "Create and schedule a Process running the code in the receiver." > ^self forkAt: Processor activePriority > >BlockContext>>forkAt: priority > "Create and schedule a Process running the code in the receiver > at the given priority. Answer the newly created process." > | forkedProcess helperProcess | > forkedProcess := self newProcess. > forkedProcess priority: priority. > priority = Processor activePriority ifTrue:[ > helperProcess := [forkedProcess resume] newProcess. > helperProcess priority: priority-1. > helperProcess resume. > ] ifFalse:[ > forkedProcess resume > ]. > ^forkedProcess > >This will make sure that #fork has (for the purpose of resumption) the >same semantics as forking at a lower priority has. > >What do people think about this? > >Cheers, > - Andreas > Louis LaBrunda Keystone Software Corp. SkypeMe callto://PhotonDemon mailto:[hidden email] http://www.Keystone-Software.com |
In reply to this post by Andreas.Raab
> Err, it is not what? Deterministic? Or safe? The point about it being > deterministic did not relate to when exactly the process would resume > (no real-time guarantee) but rather that it would resume > deterministically in relation to its parent process (in this case, only > after the parent process got suspended). Not completely deterministic. The behavior may change depending on whether there are other processes or not, that run at the active process priority. What if you instead bumped a little the priority of the active process until the forked process started running? Paolo |
In reply to this post by Louis LaBrunda
> I ran into a similar problem in VAST that I got around by extending Block and > Process. I extended Block with #forkReady, #forkReadyAt: and some others and > Process with #ready. The #forkReady extension to Block basically does what it > says, it creates a new fork or process in the ready state by does not start it > immediately. The new process will wait for its normal turn to start. The > #ready extension to Process makes the process ready without running it > immediately and is used by #forkReady. I bet that your #forkReady has the same race condition problem that Andreas is trying to solve in Squeak's #fork. :-( Paolo |
Let's look at the fork from theoretical POV, not practical implementation.
When developer puts fork in code, he supposing that execution will spawn another, parallel process: /-------- original process --------- \-------- spawned fork --------> time Suppose, in ideal environment both processes start running in parallel, and there is no scheduling, preemption and another gory details. So, it's obvious: if you need some state to be consistent in both execution threads, you should make sure to initialize it before fork happens. Andreas gave a good example of typical error, when you writing a concurrent code: trying to initialize the state after doing fork, making assumption that we not working under ideal environment and bound solution to specific implementation (knowledge on how scheduler works e.t.c.). Also, if changes, what Andreas proposed will take place, then fork will be not a fork anymore (in original context), since it's semantics puts much favor on execution of original process. Also, in case when you will need/want to port your code on another smalltalk dialect, your code have a good chances to not work correctly. And if you may know, catching such errors can take a lot of time. -- Best regards, Igor Stasenko AKA sig. |
In reply to this post by Paolo Bonzini-2
Paolo Bonzini wrote:
>> Err, it is not what? Deterministic? Or safe? The point about it being >> deterministic did not relate to when exactly the process would resume >> (no real-time guarantee) but rather that it would resume >> deterministically in relation to its parent process (in this case, >> only after the parent process got suspended). > > Not completely deterministic. The behavior may change depending on > whether there are other processes or not, that run at the active process > priority. It is entirely deterministic in such that the forked process will not be resumed until the parent process is suspended. No amount of other processes change that; even if they take 100% CPU it won't change the fact that the process will not be resumed before its parent is suspended. > What if you instead bumped a little the priority of the active process > until the forked process started running? And how does the parent process know that the forked process is running? Cheers, - Andreas |
In reply to this post by Paolo Bonzini-2
On Tue, 05 Feb 2008 22:12:37 +0100, Paolo Bonzini <[hidden email]> wrote:
>> I ran into a similar problem in VAST that I got around by extending Block and >> Process. I extended Block with #forkReady, #forkReadyAt: and some others and >> Process with #ready. The #forkReady extension to Block basically does what it >> says, it creates a new fork or process in the ready state by does not start it >> immediately. The new process will wait for its normal turn to start. The >> #ready extension to Process makes the process ready without running it >> immediately and is used by #forkReady. > >I bet that your #forkReady has the same race condition problem that >Andreas is trying to solve in Squeak's #fork. :-( > >Paolo > I haven't shown any code because VAST and Squeak are different enough in this area that my #forkReady code would not be helpful but if there is still a race condition is should be greatly reduced because the #forkReady code puts the newly created process in the queue of ready processes and returns. The setting of the instance variable would have to be interrupted immediately by a task switch, caused by a timer or some other interrupt. At least the task switch would not because by the fork code itself. Andreas' suggested code looks like it is trying to delay the task switch to the new process by what seems to me to be a somewhat convoluted means (sorry Andreas, no offence intended). Lou ----------------------------------------------------------- Louis LaBrunda Keystone Software Corp. SkypeMe callto://PhotonDemon mailto:[hidden email] http://www.Keystone-Software.com |
Louis LaBrunda wrote:
> I haven't shown any code because VAST and Squeak are different enough in this > area that my #forkReady code would not be helpful but if there is still a race > condition is should be greatly reduced because the #forkReady code puts the > newly created process in the queue of ready processes and returns. The setting > of the instance variable would have to be interrupted immediately by a task > switch, caused by a timer or some other interrupt. Which is *precisely* the problem we're talking about. Can you imagine how annoying it is that the code works in all your tests and when you ship it to a couple hundred customers it blows up just for statistical reasons? Cheers, - Andreas |
In reply to this post by Paolo Bonzini-2
On Feb 5, 2008, at 1:12 PM, Paolo Bonzini wrote: > >> I ran into a similar problem in VAST that I got around by extending >> Block and >> Process. I extended Block with #forkReady, #forkReadyAt: and some >> others and >> Process with #ready. The #forkReady extension to Block basically >> does what it >> says, it creates a new fork or process in the ready state by does >> not start it >> immediately. The new process will wait for its normal turn to >> start. The >> #ready extension to Process makes the process ready without running >> it >> immediately and is used by #forkReady. > > I bet that your #forkReady has the same race condition problem that > Andreas is trying to solve in Squeak's #fork. :-( I think you misunderstand (unless I misunderstand)... it sounds like the Process created by #forkReady doesn't start until explicitly resumed: worker := [self workerLoop] forkReady. worker resume. Also, Andreas is not trying to solve a race condition (for example, the race condition still exists if you fork at a higher priority). He's just trying to make it deterministic: make it either work all the time (as with forking a lower-priority process, or with Andreas's patch, a process of the same priority), or fail all of the time (as with forking a higher-priority process). This makes it easier to debug than having something that works 9999 times out of 10000. Josh > > > Paolo > |
On Feb 5, 2008, at 2:57 PM, Joshua Gargus wrote: > > On Feb 5, 2008, at 1:12 PM, Paolo Bonzini wrote: > >> >>> I ran into a similar problem in VAST that I got around by >>> extending Block and >>> Process. I extended Block with #forkReady, #forkReadyAt: and some >>> others and >>> Process with #ready. The #forkReady extension to Block basically >>> does what it >>> says, it creates a new fork or process in the ready state by does >>> not start it >>> immediately. The new process will wait for its normal turn to >>> start. The >>> #ready extension to Process makes the process ready without >>> running it >>> immediately and is used by #forkReady. >> >> I bet that your #forkReady has the same race condition problem that >> Andreas is trying to solve in Squeak's #fork. :-( > > I think you misunderstand (unless I misunderstand)... it sounds like > the Process created by #forkReady doesn't start until explicitly > resumed: I'm wrong again, I misunderstood how #forkReady is supposed to work. I don't know how the VAST scheduler works, so maybe there is no race- condition, but it sounds very similar to the Squeak race-condition that we're talking about. I should really stop posting on this topic. Josh >> >> >> Paolo >> > > |
In reply to this post by Bert Freudenberg
On Feb 5, 2008 11:02 PM, Bert Freudenberg <[hidden email]> wrote:
Andreas's original code was buggy and his proposed fix was incorrect. You should never make any assumptions about the scheduling behaviour of your environment. It can and will change in the future. Also, it makes your code less portable across dialects. Gulik. -- http://people.squeakfoundation.org/person/mikevdg http://gulik.pbwiki.com/ |
Michael van der Gulik wrote:
> Andreas's original code was buggy and his proposed fix was incorrect. If I would need any more proof that people don't get what I'm trying to fix, this would do it ;-) Seriously, you *really* don't get what I'm trying to address with the fix. Anyway, I don't care. Croquet has that fix and it's your choice to ignore it and deal with the consequences. And with that I'm out of this thread for real and apologize for the pointless waste of bandwidth. Cheers, - Andreas |
In reply to this post by Andreas.Raab
>> Not completely deterministic. The behavior may change depending on >> whether there are other processes or not, that run at the active >> process priority. > > It is entirely deterministic in such that the forked process will not be > resumed until the parent process is suspended. No amount of other > processes change that; even if they take 100% CPU it won't change the > fact that the process will not be resumed before its parent is suspended. I'm talking about starvation, due to the forked process not entering Squeak's round-robin scheduling at the given priority. It cannot happen without your patch, and can with. >> What if you instead bumped a little the priority of the active process >> until the forked process started running? > > And how does the parent process know that the forked process is running? The forked process could reset the priority of the parent process: fork ^self forkAt: (Processor activePriority bitAnd: -2) forkAt: priority Processor activePriority = priority ifTrue: [ p := Processor activeProcess. p priority: (priority bitOr: 1). forkedProcess := [p priority = (priority bitOr: 1) ifTrue: [p priority: priority]. self value] newProcess. ] ifFalse: [ forkedProcess := self newProcess ]. forkedProcess priority: priority. forkedProcess resume. ^forkedProcess Maybe this causes a different kind of race condition though. Paolo |
In reply to this post by Andreas.Raab
On 2/6/08, Andreas Raab <[hidden email]> wrote:
> Michael van der Gulik wrote: > > Andreas's original code was buggy and his proposed fix was incorrect. > > If I would need any more proof that people don't get what I'm trying to > fix, this would do it ;-) Seriously, you *really* don't get what I'm > trying to address with the fix. > > Anyway, I don't care. Croquet has that fix and it's your choice to > ignore it and deal with the consequences. And with that I'm out of this > thread for real and apologize for the pointless waste of bandwidth. Sorry; I think my original email was a bit blunt and direct. No personal attack was intended and I do have a lot of respect for you. Gulik. -- http://people.squeakfoundation.org/person/mikevdg http://gulik.pbwiki.com/ |
In reply to this post by Andreas.Raab
On Feb 5, 2008, at 7:51 PM, Andreas Raab wrote: > Paolo Bonzini wrote: >>> That's part of the reason why I won't pursue these changes here. >>> To me these changes are just as important as the ones that I >>> posted for Delay and Semaphore. However, unless one understands >>> the kinds of problems that are caused by the current code it is >>> pointless to argue that fixing them is important - I'm sure that >>> unless people had been bitten by Delay and Semaphore we would have >>> the same kinds of debates with all sorts of well-meant advise on >>> how you "ought" to write your code ;-) >> It's not that I don't think it's important. I think the *bugs* are >> important to fix, but that the root cause just *cannot* be fixed. > > This completely depends on your definition of "root cause" and > "cannot". For me, it's the fact that fork will behave in 99.99% of > the cases in one way and in 0.01% in a different way. That kind of > non-determinism is probably the root cause for many lingering bugs > in our system and it *can* be eliminated. > >> It's just that: >> 1) the many people who made the same mistake maybe were just >> cutting'n'pasting buggy code; > > That is of course a possibility but unless you think the majority of > people recognized the bug in the code snippet I posted, I fail to > see how this makes a difference. > >> 2) especially, the fix is not 100% safe unless I'm mistaken. > > What do you mean by "100% safe"? It is 100% deterministic (which is > what I care about); I'm not sure what you mean when you use the term > "safe" here. It may be a stupid remark but I try :) Even with your fix that could be not safe if the event handler have a higher priority. Am I right? > > > Cheers, > - Andreas > Mth |
In reply to this post by Michael van der Gulik-2
On Feb 6, 2008, at 0:08 , Michael van der Gulik wrote:
> You should never make any assumptions about the scheduling > behaviour of your environment. Nonsense. Squeak *defines* the scheduling behavior. That's one, maybe even *the* major benefit over system threads. - Bert - |
On 2/7/08, Bert Freudenberg <[hidden email]> wrote:
> On Feb 6, 2008, at 0:08 , Michael van der Gulik wrote: > > > You should never make any assumptions about the scheduling > > behaviour of your environment. > > Nonsense. Squeak *defines* the scheduling behavior. That's one, maybe > even *the* major benefit over system threads. Er... I can't tell if you're joking or if you're serious. I always write my code under the assumption that some day, in the future, some bright spark will write a better VM that will run separate Smalltalk Processes on separate OS processes/threads by whatever means, thus better utilising multi-cored or multiple CPUs. When that day comes, my code will run faster and your code will break. Gulik. -- http://people.squeakfoundation.org/person/mikevdg http://gulik.pbwiki.com/ |
In reply to this post by Andreas.Raab
Andreas Raab a écrit :
> > Anyway, I don't care. Croquet has that fix and it's your choice to > ignore it and deal with the consequences. And with that I'm out of this > thread for real and apologize for the pointless waste of bandwidth. > Yeah, after discussing for so long licence issues, underscores, the future of this or whatever, we sure cannot waste any more time on pragmatic things. Your change has my vote, would you be kind to open a Mantis issue? Any interest in completing the api with opposite alternative by way of a helperProcess at priority+1? Don't know how to name it, #forkFront ? Nicolas > Cheers, > - Andreas > > |
In reply to this post by Michael van der Gulik-2
Michael van der Gulik a écrit :
> Er... I can't tell if you're joking or if you're serious. > > I always write my code under the assumption that some day, in the > future, some bright spark will write a better VM that will run > separate Smalltalk Processes on separate OS processes/threads by > whatever means, thus better utilising multi-cored or multiple CPUs. > > When that day comes, my code will run faster and your code will break. > I see, sustainable development. Your writing code for our children. > Gulik. > |
On 06/02/2008, nicolas cellier <[hidden email]> wrote:
> Michael van der Gulik a écrit : > > Er... I can't tell if you're joking or if you're serious. > > > > I always write my code under the assumption that some day, in the > > future, some bright spark will write a better VM that will run > > separate Smalltalk Processes on separate OS processes/threads by > > whatever means, thus better utilising multi-cored or multiple CPUs. > > > > When that day comes, my code will run faster and your code will break. > > > > I see, sustainable development. Your writing code for our children. > You need a ground, based on which you can build your application. You need some API with clear rules for play. And these rules defined by shared principles and common terms, easily understandable by people. Newcomer who will start doing things should not spend time harvesting, how #fork works on particular squeak version. So, when i say, #fork, it should fork today, and should fork tomorrow, and should fork after 20 years. Because it's a generic rule, and API should behave as generic rule dictates: do whatever it take to make two processes run in parallel. But if you start doing reverse: let API influence rules, then what you will get in result? 10 APIs and each defining own semantics of fork? -- Best regards, Igor Stasenko AKA sig. |
On 2/7/08, Igor Stasenko <[hidden email]> wrote:
> On 06/02/2008, nicolas cellier <[hidden email]> wrote: > > Michael van der Gulik a écrit : > > > Er... I can't tell if you're joking or if you're serious. > > > > > > I always write my code under the assumption that some day, in the > > > future, some bright spark will write a better VM that will run > > > separate Smalltalk Processes on separate OS processes/threads by > > > whatever means, thus better utilising multi-cored or multiple CPUs. > > > > > > When that day comes, my code will run faster and your code will break. > > > > > > > I see, sustainable development. Your writing code for our children. > > > > Oh, come on, how you can be so blind? > You need a ground, based on which you can build your application. > You need some API with clear rules for play. > And these rules defined by shared principles and common terms, easily > understandable by people. > Newcomer who will start doing things should not spend time harvesting, > how #fork works on particular squeak version. > > So, when i say, #fork, it should fork today, and should fork tomorrow, > and should fork after 20 years. Because it's a generic rule, and API > should behave as generic rule dictates: do whatever it take to make > two processes run in parallel. > But if you start doing reverse: let API influence rules, then what you > will get in result? > 10 APIs and each defining own semantics of fork? Sig: I think that people are now just saying rubbish to provoke a reaction. I wouldn't bother replying. Gulik. -- http://people.squeakfoundation.org/person/mikevdg http://gulik.pbwiki.com/ |
Free forum by Nabble | Edit this page |