From e44d12e84280d0d56f84ecce6223d60e8082a99a Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Thu, 9 Apr 2026 15:36:56 +0200 Subject: [PATCH 1/3] Add example which reproduces the bug --- .../MolComponentLifeCycleTest.class.st | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Molecule-Tests/MolComponentLifeCycleTest.class.st b/src/Molecule-Tests/MolComponentLifeCycleTest.class.st index f8aa6549..2add32a5 100644 --- a/src/Molecule-Tests/MolComponentLifeCycleTest.class.st +++ b/src/Molecule-Tests/MolComponentLifeCycleTest.class.st @@ -16,6 +16,37 @@ MolComponentLifeCycleTest >> tearDown [ MolComponentManager cleanUp. ] +{ #category : #'tests - component switching' } +MolComponentLifeCycleTest >> testStartAndStopComponentSeveralTimes [ + "This test reproduce issue #246: Exception after several starts and stops of same component" + + | compA compB | + "start first implemention of a component contract" + compA := MolCompleteComponentImpl start: #compA. + compB := MolCompleteComponentImpl start: #compB. + + "link compB to compA" + compB forServices: MolUsedServices useProvider: #compA. + compB forEvents: MolUsedEvents useProducer: #compA. + compB getMolUsedEventsSubscriber subscribe: compB. + + "call the service of the first component, it answer #service" + self assert: compB getMolUsedServicesProvider service equals: #service. + + "stop and start component several times" + 1 to: 10 do:[ :i | + compB forEvents: MolUsedEvents removeProducer: #compA. + compB class stop: #compB. + compB := MolCompleteComponentImpl start: #compB. + compB forServices: MolUsedServices useProvider: #compA. + compB forEvents: MolUsedEvents useProducer: #compA. + compB getMolUsedEventsSubscriber subscribe: compB. + self assert: compB getMolUsedServicesProvider service equals: #service. + ]. + + +] + { #category : #'tests - component switching' } MolComponentLifeCycleTest >> testSwitchComponentsSearchServicesProviderFor [ "start first implemention of a component contract" From 9b38d6308e1fae3a73cd67b28b3396685f6b0c87 Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Thu, 9 Apr 2026 16:05:52 +0200 Subject: [PATCH 2/3] Rename and move example. Fix issue #246: reset the default component key in the eventSubscribers as default when a producer is removed. --- .../MolComponentImplTest.class.st | 31 +++++++++++++++++++ .../MolComponentLifeCycleTest.class.st | 31 ------------------- src/Molecule/MolComponentImpl.trait.st | 5 ++- src/Molecule/MolEventSubscriber.class.st | 23 ++++++++------ 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/Molecule-Tests/MolComponentImplTest.class.st b/src/Molecule-Tests/MolComponentImplTest.class.st index 43af80b4..55977751 100644 --- a/src/Molecule-Tests/MolComponentImplTest.class.st +++ b/src/Molecule-Tests/MolComponentImplTest.class.st @@ -673,6 +673,37 @@ MolComponentImplTest >> testStart2 [ self assert: component componentName equals: #compA. ] +{ #category : #'tests - connecting - events producers' } +MolComponentImplTest >> testStartAndStopComponentWithRemoveProducerSeveralTimes [ + "This test reproduce issue #246: Exception after several starts and stops of same component" + + | compA compB | + "start first implemention of a component contract" + compA := MolCompleteComponentImpl start: #compA. + compB := MolCompleteComponentImpl start: #compB. + + "link compB to compA" + compB forServices: MolUsedServices useProvider: #compA. + compB forEvents: MolUsedEvents useProducer: #compA. + compB getMolUsedEventsSubscriber subscribe: compB. + + "call the service of the first component, it answer #service" + self assert: compB getMolUsedServicesProvider service equals: #service. + + "stop and start component several times" + 1 to: 10 do:[ :i | + compB forEvents: MolUsedEvents removeProducer: #compA. + compB class stop: #compB. + compB := MolCompleteComponentImpl start: #compB. + compB forServices: MolUsedServices useProvider: #compA. + compB forEvents: MolUsedEvents useProducer: #compA. + compB getMolUsedEventsSubscriber subscribe: compB. + self assert: compB getMolUsedServicesProvider service equals: #service. + ]. + + +] + { #category : #'tests - component creation' } MolComponentImplTest >> testStartWithGeneratedName [ diff --git a/src/Molecule-Tests/MolComponentLifeCycleTest.class.st b/src/Molecule-Tests/MolComponentLifeCycleTest.class.st index 2add32a5..f8aa6549 100644 --- a/src/Molecule-Tests/MolComponentLifeCycleTest.class.st +++ b/src/Molecule-Tests/MolComponentLifeCycleTest.class.st @@ -16,37 +16,6 @@ MolComponentLifeCycleTest >> tearDown [ MolComponentManager cleanUp. ] -{ #category : #'tests - component switching' } -MolComponentLifeCycleTest >> testStartAndStopComponentSeveralTimes [ - "This test reproduce issue #246: Exception after several starts and stops of same component" - - | compA compB | - "start first implemention of a component contract" - compA := MolCompleteComponentImpl start: #compA. - compB := MolCompleteComponentImpl start: #compB. - - "link compB to compA" - compB forServices: MolUsedServices useProvider: #compA. - compB forEvents: MolUsedEvents useProducer: #compA. - compB getMolUsedEventsSubscriber subscribe: compB. - - "call the service of the first component, it answer #service" - self assert: compB getMolUsedServicesProvider service equals: #service. - - "stop and start component several times" - 1 to: 10 do:[ :i | - compB forEvents: MolUsedEvents removeProducer: #compA. - compB class stop: #compB. - compB := MolCompleteComponentImpl start: #compB. - compB forServices: MolUsedServices useProvider: #compA. - compB forEvents: MolUsedEvents useProducer: #compA. - compB getMolUsedEventsSubscriber subscribe: compB. - self assert: compB getMolUsedServicesProvider service equals: #service. - ]. - - -] - { #category : #'tests - component switching' } MolComponentLifeCycleTest >> testSwitchComponentsSearchServicesProviderFor [ "start first implemention of a component contract" diff --git a/src/Molecule/MolComponentImpl.trait.st b/src/Molecule/MolComponentImpl.trait.st index fcde883d..e52ac50d 100644 --- a/src/Molecule/MolComponentImpl.trait.st +++ b/src/Molecule/MolComponentImpl.trait.st @@ -348,7 +348,10 @@ MolComponentImpl >> forEvents: anEventsTrait removeProducer: aComponentName [ producers := self eventsSubscribers at: anEventsTrait. producers ifNil:[ ^self ]. - producers = aComponentName ifTrue:[ self eventsSubscribers at: anEventsTrait put: nil. ^ self ]. + producers = aComponentName ifTrue:[ + "Molecule issue #242: store the default component name when a producer is removed to going back to the initial state" + self eventsSubscribers at: anEventsTrait put: MolUtils defaultComponentName. ^ self + ]. (producers isArray and:[producers includes: aComponentName]) ifTrue: [ | newProducers | newProducers := producers asOrderedCollection copy. newProducers remove: aComponentName. diff --git a/src/Molecule/MolEventSubscriber.class.st b/src/Molecule/MolEventSubscriber.class.st index 84122ad5..d9eaa2e3 100644 --- a/src/Molecule/MolEventSubscriber.class.st +++ b/src/Molecule/MolEventSubscriber.class.st @@ -61,15 +61,20 @@ MolEventSubscriber >> connectOriginator: componentName to: aComponent [ "Connect a component to event pipeline" | key connect | - key := aComponent eventsSubscribers at: self events ifAbsent: [ nil ]. - connect := key isSymbol ifTrue:[ componentName = key ] ifFalse:[ key includes: componentName ]. - - connect ifTrue: [ - self events allSelectors do: [ :event | - | originator | - originator := self originatorsLinks at: componentName. - originator ifNil: [ ^ self error: 'Component originator is nil' ]. - originator when: event send: event to: aComponent ] ] + key := aComponent eventsSubscribers at: self events ifAbsent: [ MolUtils defaultComponentName ]. + + connect := key isSymbol + ifTrue:[ componentName = key ] + ifFalse:[ key includes: componentName ]. + + "Cannot connect, stop here" + connect ifFalse:[ ^ self ]. + + self events allSelectors do: [ :event | + | originator | + originator := self originatorsLinks at: componentName. + originator ifNil: [ ^ self error: 'Component originator is nil' ]. + originator when: event send: event to: aComponent ] ] { #category : #private } From 172241dccacd567cad9ad67d83f03ec352e33780 Mon Sep 17 00:00:00 2001 From: Pierre Laborde Date: Thu, 9 Apr 2026 16:26:57 +0200 Subject: [PATCH 3/3] Change behavior due to #246 --- src/Molecule-Tests/MolComponentImplTest.class.st | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Molecule-Tests/MolComponentImplTest.class.st b/src/Molecule-Tests/MolComponentImplTest.class.st index 55977751..67a6ae05 100644 --- a/src/Molecule-Tests/MolComponentImplTest.class.st +++ b/src/Molecule-Tests/MolComponentImplTest.class.st @@ -234,6 +234,8 @@ MolComponentImplTest >> testForEventsRemoveProducer [ | component | component := MolCompleteComponentImpl start: #myComponentA. + "initial state" + self assert: (component eventsSubscribers at: MolUsedEvents) equals: MolUtils defaultComponentName. component forEvents: MolUsedEvents useAllProducers: #(#producerA #producerB #producerC). @@ -247,7 +249,8 @@ MolComponentImplTest >> testForEventsRemoveProducer [ component forEvents: MolUsedEvents removeProducer: #producerC. self assert: component eventsSubscribers size equals: 1. - self assert: (component eventsSubscribers at: MolUsedEvents) equals: nil. + "back to initial state" + self assert: (component eventsSubscribers at: MolUsedEvents) equals: MolUtils defaultComponentName. ] { #category : #'tests - connecting - events producers' }