_BACKGROUND_

In relation to...
https://pharo.fogbugz.com/default.asp?13182
"Refactor MCFileRepositoryInspector >> versionSearchAccept:"

I am reviewing...
https://pharo.fogbugz.com/default.asp?13007
"Cannot integrate a slice with monticello (red square in the UI)"

and in general considering how Monticello uses multiple threads to allow you to type in the filter while its downloading info from the remote repository (which would be good to have more of this sort thing).

Looking back at build 30814 just prior to slice 13007 being integrated,
I notice that MCFileRepositoryInspector>>setRepository:workingCopy: [1]
forks and sends #refresh, which sends #changed: #packageList.

and MCFileRepositoryInspector>>packageSearchAccept: [2]
also forks and sends #changed: #packageList

So there were three threads (including UI [3]) modifying object state, which was compounded by #packageList method doing a lot of work. Issue 13007 worked around this by moving the work of #packageList out to #packageListUpdate, and moved the #changed: sends from [1] & [2] also out into #packageListUpdate (and wrapped in a #defer:).


_QUESTIONS_

Now I am considering doing the same for issue 13182 #versionSearchAccept:, and considering Pharo's goal is to replace all Dependents/#changed: mechanisms with Announcements.

1. So I am wondering if that would be a better direction to address this?

Then I wonder about how Announcements interact with threads. I see that SubscriptionRegistry>>deliver: #protect's the generation of /interestedSubscriptions/, but the actual announcement delivery occurs in the open, in AnnoucemenSubscription>>delvier:,
in the sender's thread.

So for an example...

    action := [ :it | self inform: 'removing child'.].
    announcer on: RemoveChild do: action.

/action/ might be executed from different concurrent threads.

Now you might say that the programmer should know enough about the structure of their threaded code to wrap the action in as critical section, but Announcements are a DECOUPLING mechanism, and maybe a THIRD PARTY library subscribes to an Announcement that later someone else wraps in a fork, and no-one notices possible race conditions created.

2. So, is there something reasonable we can do to make the common/easier/safer single-threaded case the default, and make people work a bit harder to shoot themselves in the foot with multiple threads?

We could introduce Announcer>>onOtherThread:do:
with the purpose of making use of inter-thread announcements more explicit in a self documenting way.

Announcements accidentally passed between threads to Announcer>>on:do: would generate an exception advising "use #onOtherThread:do: "
with the reasonable expectation that use of #onOtherThread:do:
would follow the pattern...

    announcer onOtherThread: RemoveChild do: [ :it |
        myMutex critical: [ self inform: 'removing child' ].
        ].

With this arrangement, a third party subscribed to someone else's announcement, that is quietly changed to be wrapped by forked thread, the third party's code now fails EARLY and OBVIOUS, rather than letting subtle race conditions occur at random times.


_PROPOSED IMPLEMENTATION_

"------------------"

    Announcer>>subscribe: anAnnouncementClass do: aValuable
        ^ registry add: (
                AnnouncementSubscription new
                        announcer: self;
                        process: Processor activeProcess
                        announcementClass: anAnnouncementClass;
                        valuable: aValuable)
"------------------"

    AnnouncementSubscription>>deliver: anAnnouncement
        process ~= Processor activeProcess ifTrue: [
self error: [ 'Cross thread announcements should subscribe using #onOtherThread:do: ' ].
        ^ (self handlesAnnouncement: anAnnouncement ) ifTrue: [
                [action cull: anAnnouncement cull: announcer]
                        on: UnhandledError fork: [:ex | ex pass ]]
"------------------"

    Announcer>>threadedSubscribe: anAnnouncementClass do: aValuable
        ^ registry add: (
                ThreadedAnnouncementSubscription new
                        announcer: self;
                        process: Processor activeProcess
                        announcementClass: anAnnouncementClass;
                        valuable: aValuable)
"------------------"

where ThreadedAnnouncementSubscription essentially functions the same as the existing AnnoucementSubscription .



Reply via email to