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 |
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 |
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 |
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 |
Free forum by Nabble | Edit this page |