Another MessageBox forkMainIfMain problem

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Another MessageBox forkMainIfMain problem

Bill Dargel
I recently changed what prompting gets done when closing one of my
application's shells, if there are unsaved changes. Managed to uncover a
timing-dependent problem in 5.1.4 that arises from having more than one
process dealing with Windows messages. Good old #forkMainIfMain.

Figuring out what was going on sure wasn't much fun :-(  [When adding
instrumentation can change the timing enough that the problem goes
away]. I think that I've got a reasonable analysis of what's going on...
-----
Main UI Process #1, priority 5:
        ShellView>>onCloseRequested
        MyShell>>onCloseRequested:
        MySubPresenter>>onPromptToSaveChanges:
        MessageBox>>confirm:
                (new main UI process #2 forked, priority 5)
        ... confirmation.
Old process #1 continues at priority 6:
        changes to be saved get put on a SharedQueue
        [1] process blocks until signaled that save was successful

Main UI process #2, priority 5:
        View>>wmPaint:wParam:lParam: on the original ShellView
        View>>validateLayout
        suspended when process #1 wakes up

Process #1, priority 6, unblocks:
        changes were saved successfully, so returns
        ShellView>>onCloseRequested continues
        View>>destroy
        View>>wmNcDestroy:wParam:lParam:
        turning various aspects of the view into DeafObjects

Main UI Process #2 continues:
        View>>validateLayout
        ...
        ContainerView class(View class)>>mapRectangle:from:to:
        UserLibrary>>mapWindowPoints:hWndTo:lpPoints:cPoints:

Boom: 'Invalid arg 2: Cannot coerce a DeafObject to handle'

-----

I'm looking for any wisdom on the best way to avoid the problem.

I'm using a non-task-modal MessageBox, essentially the same as
DocumentShell>>onPromptToSaveChanges: does. Changing this to task-modal
seems to take care of things, by never creating a second UI process. The
blocking at [1] will make use of a ModalMsgLoop in that case, to keep
the message pump going. Though, I really hate to make UI prompts more
modal than they really need to be.

The blocking [1] makes the processing after the message box confirmation
asynchronous, and ends up allowing the new UI process a chance to start
on the paint message, which will later have problems. The blocking is
needed though, since I want to be able to cancel the closing of the
shell if there's a problem with the save.

It seems that the expectation, or at least hope, is that the process
running after a MessageBox gets dismissed will quickly do its thing and
then go away without causing trouble for the forked main UI process.
That would seem to be a hard thing to enforce.
ShellView>>onCloseRequested is a case in point -- where after triggering
possible confirmation, it goes on to actually destroy the view.

Would it be possible to "undo" the effect of #forkMainIfMain? After a
MessageBox gets dismissed, do some sort of synchronization with the main
UI process, killing it off and turning the original process back into
the main UI process? Hmm, seems like that might be a good way to
eliminate these sorts of traps.

Any thoughts?

--
Bill Dargel            [hidden email]
Shoshana Technologies
100 West Joy Road, Ann Arbor, MI 48105  USA


Reply | Threaded
Open this post in threaded view
|

Re: Another MessageBox forkMainIfMain problem

Chris Uppal-3
Bill Dargel wrote:

> Boom: 'Invalid arg 2: Cannot coerce a DeafObject to handle'

Nasty...


> I'm looking for any wisdom on the best way to avoid the problem.

Dunno if it's "wise", but one possible workaround would be for your first
#onCloseRequested: to refuse the close request even if the save worked, but
instead to issue a new #close (or #exit ?) request.  I suspect it would be
better to #postToInputQueue:, though I don't really know if that's necessary.


> It seems that the expectation, or at least hope, is that the process
> running after a MessageBox gets dismissed will quickly do its thing and
> then go away without causing trouble for the forked main UI process.
> That would seem to be a hard thing to enforce.
> ShellView>>onCloseRequested is a case in point -- where after triggering
> possible confirmation, it goes on to actually destroy the view.

And now I'm wondering if it's just luck that such problems don't occur more
often...


> Would it be possible to "undo" the effect of #forkMainIfMain? After a
> MessageBox gets dismissed, do some sort of synchronization with the main
> UI process, killing it off and turning the original process back into
> the main UI process? Hmm, seems like that might be a good way to
> eliminate these sorts of traps.

A sort of:
    ProcessScheduler>>forkMainIfMainWhile: aBlock
method.  Sounds quite usable to me.  I don't know if it's implementable; it
/looks/ as if it might be as simple as making the originating main thread stuff
its identity back into the current InputState's #main before returning back out
to its own #mainLoop.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Another MessageBox forkMainIfMain problem

Bill Dargel
Chris Uppal wrote:
> Dunno if it's "wise", but one possible workaround would be for your first
> #onCloseRequested: to refuse the close request even if the save worked, but
> instead to issue a new #close (or #exit ?) request.  I suspect it would be
> better to #postToInputQueue:, though I don't really know if that's necessary.

That's a clever idea. Thanks. It should handle the particular problem
that I ran into. Just might be what I use as my workaround, unless a
more comprehensive solution comes out. (I'm hoping that maybe Blair will
weigh-in at some point).

> And now I'm wondering if it's just luck that such problems don't occur more
> often...

Good. I was worried that maybe I was the only one. ;-)

>>Would it be possible to "undo" the effect of #forkMainIfMain? After a
>>MessageBox gets dismissed, do some sort of synchronization with the main
>>UI process, killing it off and turning the original process back into
>>the main UI process? Hmm, seems like that might be a good way to
>>eliminate these sorts of traps.
>
> A sort of:
>     ProcessScheduler>>forkMainIfMainWhile: aBlock
> method.  Sounds quite usable to me.  I don't know if it's implementable; it
> /looks/ as if it might be as simple as making the originating main thread stuff
> its identity back into the current InputState's #main before returning back out
> to its own #mainLoop.

It would be great if something "official" along these lines could be
worked out. It seems like a general usage #forkMainIfMainWhile: method
would still need to do some sort of synchronization with the forked UI
process to be sure the stand-in had gotten back to a quiescent point in
its mainLoop, so that the two processes don't interfere. Though perhaps
in the specialized case of returning from a MessageBox, things might
already be naturally synced? Where it could be as simple as you suggest?
I dunno. Someone more knowledgeable than I in the mysteries of the
working of Windows would be needed.

--
Bill Dargel            [hidden email]
Shoshana Technologies
100 West Joy Road, Ann Arbor, MI 48105  USA


Reply | Threaded
Open this post in threaded view
|

Re: Another MessageBox forkMainIfMain problem

Chris Uppal-3
Bill Dargel wrote:

> > A sort of:
> >     ProcessScheduler>>forkMainIfMainWhile: aBlock
> > method.  Sounds quite usable to me.  I don't know if it's
> > implementable; it /looks/ as if it might be as simple as making the
> > originating main thread stuff its identity back into the current
> > InputState's #main before returning back out to its own #mainLoop.
>
> It would be great if something "official" along these lines could be
> worked out.

I'm liking the idea more and more too.


> It seems like a general usage #forkMainIfMainWhile: method
> would still need to do some sort of synchronization with the forked UI
> process to be sure the stand-in had gotten back to a quiescent point in
> its mainLoop, so that the two processes don't interfere.

Yes, it might not be necessary in practise (after all the existing system
doesn't often cause problems), but for full generality I think you'd need some
sort of "baton" system, where processes passed around the right to be "main".
So:

    forkMainIfMain
        If I hold the baton and no other process is waiting to take it,
        then relinquish the baton, fork a new proto-main process (see below),
        and return true.
        If I hold the baton and another process is wanting to take it,
        pass the baton to that process and return true.
        If I don't hold the baton then just return false.

    forkMainIfMainWhle: aBlock
       wasMain := self forkMainIfMain.
       evaluate aBlock, ensuring:
       if wasMain then wait (blocking further execution) until I
       can re-acquire the baton.

    protoMainLoop
        attempt to acquire the baton (non-blocking).  If I
        succeed then enter a main loop which continues
        until some other process does want to take the
        baton off me.

The "baton" would, I assume, have to be properly protected for access by
multiple Processes (unless some detail of the VM's implementation makes that
unnecessary in practise).  That raises the possibility of locking up the system
if the baton holder becomes un-responsive, so I suppose there would have to be
some sort of emergency #grabTheBatton operation available for the debugger to
use, or at least to be part of the <ctrl>+<break> handler.

Not as simple as my first thought, but (I think) still worth considering.
Andy? Blair?

    -- chris