Fixes #586: Use immutable listener lists to safely change them during… · cloudsimplus/cloudsimplus@0813444 · GitHub
Skip to content

Commit 0813444

Browse files
manoelcamposclaude
andauthored
Fixes #586: Use immutable listener lists to safely change them during notification (PR #593)
Listener lists in VmAbstract, HostAbstract, DatacenterBrokerAbstract and CloudSimPlus are now immutable lists replaced on each change, using the new Listeners helper class. A for-each loop that is notifying listeners keeps going over the old list, so: - no ConcurrentModificationException is thrown; - a listener removing itself doesn't make the next one be skipped (as happened with the indexed for workaround); - a listener added during notification is only called in the next round. Lists start as the shared List.of(), so objects without listeners don't allocate any empty ArrayList/HashSet (Optimization 1 of #523 for VMs and Hosts). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c85f8eb commit 0813444

9 files changed

Lines changed: 475 additions & 94 deletions

File tree

‎pom.xml‎

Lines changed: 14 additions & 1 deletion

‎src/main/java/org/cloudsimplus/brokers/DatacenterBrokerAbstract.java‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.cloudsimplus.listeners.DatacenterBrokerEventInfo;
2525
import org.cloudsimplus.listeners.EventInfo;
2626
import org.cloudsimplus.listeners.EventListener;
27+
import org.cloudsimplus.listeners.Listeners;
2728
import org.cloudsimplus.schedulers.cloudlet.CloudletScheduler;
2829
import org.cloudsimplus.traces.google.GoogleTaskEventsTraceReader;
2930
import org.cloudsimplus.util.InvalidEventDataTypeException;
@@ -65,7 +66,7 @@ public non-sealed abstract class DatacenterBrokerAbstract extends CloudSimEntity
6566
///
6667
/// @see #addOnVmsCreatedListener(EventListener)
6768
@Getter(AccessLevel.NONE)
68-
private final List<EventListener<DatacenterBrokerEventInfo>> onVmsCreatedListeners;
69+
private List<EventListener<DatacenterBrokerEventInfo>> onVmsCreatedListeners = List.of();
6970

7071
private final VmCreation vmCreation;
7172

@@ -154,7 +155,6 @@ public DatacenterBrokerAbstract(final CloudSimPlus simulation, final String name
154155
setName(name);
155156
}
156157

157-
this.onVmsCreatedListeners = new ArrayList<>();
158158
this.lastSubmittedCloudlet = Cloudlet.NULL;
159159
this.lastSubmittedVm = Vm.NULL;
160160
this.lastSelectedVm = Vm.NULL;
@@ -670,10 +670,7 @@ private void notifyOnVmsCreatedListeners() {
670670
return;
671671
}
672672

673-
// TODO: Workaround - Uses indexed for to avoid ConcurrentModificationException
674-
// See https://github.com/cloudsimplus/cloudsimplus/issues/586
675-
for (int i = 0; i < onVmsCreatedListeners.size(); i++) {
676-
final var listener = onVmsCreatedListeners.get(i);
673+
for (final var listener : onVmsCreatedListeners) {
677674
listener.update(DatacenterBrokerEventInfo.of(listener, this));
678675
}
679676

@@ -1208,13 +1205,13 @@ protected Datacenter getDatacenter(final Vm vm) {
12081205

12091206
@Override
12101207
public DatacenterBroker addOnVmsCreatedListener(@NonNull final EventListener<DatacenterBrokerEventInfo> listener) {
1211-
this.onVmsCreatedListeners.add(listener);
1208+
onVmsCreatedListeners = Listeners.add(onVmsCreatedListeners, listener);
12121209
return this;
12131210
}
12141211

12151212
@Override
12161213
public DatacenterBroker removeOnVmsCreatedListener(@NonNull final EventListener<? extends EventInfo> listener) {
1217-
this.onVmsCreatedListeners.remove(listener);
1214+
onVmsCreatedListeners = Listeners.remove(onVmsCreatedListeners, listener);
12181215
return this;
12191216
}
12201217

‎src/main/java/org/cloudsimplus/core/CloudSimPlus.java‎

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
import org.cloudsimplus.core.events.SimEvent;
2828
import org.cloudsimplus.listeners.EventInfo;
2929
import org.cloudsimplus.listeners.EventListener;
30+
import org.cloudsimplus.listeners.Listeners;
3031

31-
import java.util.HashSet;
32-
import java.util.Set;
32+
import java.util.List;
3333

3434
/// Starts and manages cloud computing simulations running in the CloudSim Plus framework.
3535
/// @author Manoel Campos da Silva Filho
@@ -40,10 +40,10 @@ public class CloudSimPlus extends CloudSim {
4040

4141
private final CircularTimeQueue clockQueue;
4242

43-
private final Set<EventListener<SimEvent>> onEventProcessingListeners;
44-
private final Set<EventListener<EventInfo>> onSimulationPauseListeners;
45-
private final Set<EventListener<EventInfo>> onClockTickListeners;
46-
private final Set<EventListener<EventInfo>> onSimulationStartListeners;
43+
private List<EventListener<SimEvent>> onEventProcessingListeners = List.of();
44+
private List<EventListener<EventInfo>> onSimulationPauseListeners = List.of();
45+
private List<EventListener<EventInfo>> onClockTickListeners = List.of();
46+
private List<EventListener<EventInfo>> onSimulationStartListeners = List.of();
4747

4848
/// Creates a CloudSim Plus simulation.
4949
/// Internally, it creates a [CloudInformationService].
@@ -63,10 +63,6 @@ public CloudSimPlus() {
6363
/// @see CloudInformationService
6464
public CloudSimPlus(final double minTimeBetweenEvents) {
6565
super(minTimeBetweenEvents);
66-
this.onEventProcessingListeners = new HashSet<>();
67-
this.onSimulationPauseListeners = new HashSet<>();
68-
this.onClockTickListeners = new HashSet<>();
69-
this.onSimulationStartListeners = new HashSet<>();
7066
this.clockQueue = new CircularTimeQueue(this);
7167
}
7268

@@ -115,55 +111,63 @@ protected double setClock(double newTime) {
115111
return oldTime;
116112
}
117113

118-
private void notifyEventListeners(final Set<EventListener<EventInfo>> listeners, final double clock) {
119-
listeners.forEach(listener -> listener.update(EventInfo.of(listener, clock)));
114+
private void notifyEventListeners(final List<EventListener<EventInfo>> listeners, final double clock) {
115+
for (final var listener : listeners) {
116+
listener.update(EventInfo.of(listener, clock));
117+
}
120118
}
121119

122120
@Override
123121
protected void notifyOnSimulationStartListeners() {
124122
if(!onSimulationStartListeners.isEmpty() && clock() > 0) {
125123
notifyEventListeners(onSimulationStartListeners, clock());
126124
//Since the simulation starts just once, clear the listeners to avoid them to be notified again
127-
onSimulationStartListeners.clear();
125+
onSimulationStartListeners = List.of();
128126
}
129127
}
130128

131129
@Override
132130
public Simulation addOnSimulationPauseListener(@NonNull final EventListener<EventInfo> listener) {
133-
this.onSimulationPauseListeners.add(listener);
131+
onSimulationPauseListeners = Listeners.add(onSimulationPauseListeners, listener);
134132
return this;
135133
}
136134

137135
@Override
138136
public Simulation addOnSimulationStartListener(@NonNull final EventListener<EventInfo> listener) {
139-
this.onSimulationStartListeners.add(listener);
137+
onSimulationStartListeners = Listeners.add(onSimulationStartListeners, listener);
140138
return this;
141139
}
142140

143141
@Override
144142
public boolean removeOnSimulationPauseListener(@NonNull final EventListener<EventInfo> listener) {
145-
return this.onSimulationPauseListeners.remove(listener);
143+
final var oldList = onSimulationPauseListeners;
144+
onSimulationPauseListeners = Listeners.remove(oldList, listener);
145+
return onSimulationPauseListeners.size() != oldList.size();
146146
}
147147

148148
@Override
149149
public Simulation addOnEventProcessingListener(@NonNull final EventListener<SimEvent> listener) {
150-
this.onEventProcessingListeners.add(listener);
150+
onEventProcessingListeners = Listeners.add(onEventProcessingListeners, listener);
151151
return this;
152152
}
153153

154154
@Override
155155
public boolean removeOnEventProcessingListener(@NonNull final EventListener<SimEvent> listener) {
156-
return onEventProcessingListeners.remove(listener);
156+
final var oldList = onEventProcessingListeners;
157+
onEventProcessingListeners = Listeners.remove(oldList, listener);
158+
return onEventProcessingListeners.size() != oldList.size();
157159
}
158160

159161
@Override
160162
public Simulation addOnClockTickListener(@NonNull final EventListener<EventInfo> listener) {
161-
onClockTickListeners.add(listener);
163+
onClockTickListeners = Listeners.add(onClockTickListeners, listener);
162164
return this;
163165
}
164166

165167
@Override
166168
public boolean removeOnClockTickListener(@NonNull final EventListener<? extends EventInfo> listener) {
167-
return onClockTickListeners.remove(listener);
169+
final var oldList = onClockTickListeners;
170+
onClockTickListeners = Listeners.remove(oldList, listener);
171+
return onClockTickListeners.size() != oldList.size();
168172
}
169173
}

‎src/main/java/org/cloudsimplus/hosts/HostAbstract.java‎

Lines changed: 21 additions & 17 deletions

0 commit comments

Comments
 (0)