blob: 47819cd768684e444c0967f2de6947c9b050a3ad [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dm.itest.api;
20
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Dictionary;
24import java.util.Hashtable;
25import java.util.List;
26import java.util.concurrent.ExecutorService;
27import java.util.concurrent.Executors;
28import java.util.concurrent.TimeUnit;
29
30import org.junit.Assert;
31
32import org.apache.felix.dm.Component;
33import org.apache.felix.dm.ConfigurationDependency;
34import org.apache.felix.dm.ServiceDependency;
35import org.apache.felix.dm.itest.util.Ensure;
36import org.apache.felix.dm.itest.util.TestBase;
37import org.osgi.service.cm.Configuration;
38import org.osgi.service.cm.ConfigurationAdmin;
39import org.osgi.service.cm.ConfigurationException;
40
41
42/**
43 * This test class simulates a client having many dependencies being registered/unregistered concurrently.
44 *
45 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
46 */
47@SuppressWarnings({"unchecked", "rawtypes"})
48public class ServiceRaceTest extends TestBase {
49 volatile ConfigurationAdmin m_cm;
50 final static int STEP_WAIT = 5000;
51 final static int DEPENDENCIES = 10;
52 final static int LOOPS = 3000;
53 final Ensure m_done = new Ensure(true);
54
55 // Executor used to bind/unbind service dependencies.
56 ExecutorService m_threadpool;
57
58 // Timestamp used to log the time consumed to execute 100 tests.
59 long m_timeStamp;
60
61 public interface Dep {
62 }
63
64 public class DepImpl implements Dep {
65 }
66
67 /**
68 * Creates many service dependencies, and activate/deactivate them concurrently.
69 */
70 public void testCreateParallelComponentRegistgrationUnregistration() {
71 m_dm.add(m_dm.createComponent()
72 .setImplementation(this)
73 .setCallbacks(null, "start", null, null)
74 .add(m_dm.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true)));
75 m_done.waitForStep(1, 60000);
76 m_dm.clear();
77 Assert.assertFalse(super.errorsLogged());
78 }
79
80 void start() {
81 new Thread(new Runnable() {
82 public void run() {
83 doStart();
84 }}).start();
85 }
86
87 void doStart() {
88 info("Starting createParallelComponentRegistgrationUnregistration test");
89 initThreadPool(); // only if setParallel() has not been called (only if a parallel DM is not used).
90
91 try {
92 m_timeStamp = System.currentTimeMillis();
93 for (int loop = 0; loop < LOOPS; loop++) {
94 doTest(loop);
95 }
96 }
97 catch (Throwable t) {
98 error("got unexpected exception", t);
99 }
100 finally {
101 shutdownThreadPool();
102 m_done.step(1);
103 }
104 }
105
106 private void initThreadPool() {
107 if (! m_parallel) {
108 // We are not using a parallel DM, so we create a custom threadpool in order to add components concurrently.
109 int cores = Math.max(16, Runtime.getRuntime().availableProcessors());
110 info("using " + cores + " cores.");
111 m_threadpool = Executors.newFixedThreadPool(Math.max(cores, DEPENDENCIES + 3 /* start/stop/configure */));
112 }
113 }
114
115 void shutdownThreadPool() {
116 if (! m_parallel && m_threadpool != null) {
117 m_threadpool.shutdown();
118 try {
119 m_threadpool.awaitTermination(60, TimeUnit.SECONDS);
120 } catch (InterruptedException e) {
121 }
122 }
123 }
124
125 void doTest(int loop) throws Throwable {
126 debug("loop#%d -------------------------", loop);
127
128 final Ensure step = new Ensure(false);
129
130 // Create one client component, which depends on many service dependencies
131 final Component client = m_dm.createComponent();
132 final Client clientImpl = new Client(step);
133 client.setImplementation(clientImpl);
134
135 // Create client service dependencies
136 final ServiceDependency[] dependencies = new ServiceDependency[DEPENDENCIES];
137 for (int i = 0; i < DEPENDENCIES; i++) {
138 final String filter = "(id=loop" + loop + "." + i + ")";
139 dependencies[i] = m_dm.createServiceDependency().setService(Dep.class, filter)
140 .setRequired(true)
141 .setCallbacks("add", "remove");
142 client.add(dependencies[i]);
143 }
144 String pid = "pid." + loop;
145 final ConfigurationDependency confDependency = m_dm.createConfigurationDependency().setPid(pid);
146 client.add(confDependency);
147
148 // Create Configuration (concurrently).
149 final Configuration conf = m_cm.getConfiguration(pid, null);
150 final Hashtable props = new Hashtable();
151 props.put("foo", "bar");
152 schedule(new Runnable() {
153 public void run() {
154 try {
155 conf.update(props);
156 }
157 catch (IOException e) {
158 error("update failed", e);
159 }
160 }
161 });
162
163 // Activate the client service dependencies concurrently.
164 List<Component> deps = new ArrayList();
165 for (int i = 0; i < DEPENDENCIES; i++) {
166 Hashtable h = new Hashtable();
167 h.put("id", "loop" + loop + "." + i);
168 final Component s = m_dm.createComponent()
169 .setInterface(Dep.class.getName(), h)
170 .setImplementation(new DepImpl());
171 deps.add(s);
172 schedule(new Runnable() {
173 public void run() {
174 m_dm.add(s);
175 }
176 });
177 }
178
179 // Start the client (concurrently)
180 schedule(new Runnable() {
181 public void run() {
182 m_dm.add(client);
183 }
184 });
185
186 // Ensure that client has been started.
187 int expectedStep = 1 /* conf */ + DEPENDENCIES + 1 /* start */;
188 step.waitForStep(expectedStep, STEP_WAIT);
189 Assert.assertEquals(DEPENDENCIES, clientImpl.getDependencies());
190 Assert.assertNotNull(clientImpl.getConfiguration());
191
192 // Stop all dependencies concurrently.
193 for (Component dep : deps) {
194 final Component dependency = dep;
195 schedule(new Runnable() {
196 public void run() {
197 m_dm.remove(dependency);
198 }
199 });
200 }
201
202 // Stop client concurrently.
203 schedule(new Runnable() {
204 public void run() {
205 m_dm.remove(client);
206 }
207 });
208
209 // Remove configuration (asynchronously)
210 schedule(new Runnable() {
211 public void run() {
212 try {
213 conf.delete();
214 }
215 catch (IOException e) {
216 warn("error while unconfiguring", e);
217 }
218 }
219 });
220
221 // Ensure that client has been stopped, then destroyed, then unbound from all dependencies
222 expectedStep += 2; // stop/destroy
223 expectedStep += DEPENDENCIES; // removed all dependencies
Pierre De Rop3a00a212015-03-01 09:27:46 +0000224 step.waitForStep(expectedStep, STEP_WAIT);
225 step.ensure();
226 Assert.assertEquals(0, clientImpl.getDependencies());
Pierre De Rop3a00a212015-03-01 09:27:46 +0000227
228 if (super.errorsLogged()) {
229 throw new IllegalStateException("Race test interrupted (some error occured, see previous logs)");
230 }
231
232 debug("finished one test loop");
233 if ((loop + 1) % 100 == 0) {
234 long duration = System.currentTimeMillis() - m_timeStamp;
235 warn("Performed 100 tests (total=%d) in %d ms.", (loop + 1), duration);
236 m_timeStamp = System.currentTimeMillis();
237 }
238 }
239
240 private void schedule(Runnable task) {
241 if (! m_parallel) {
242 // not using parallel DM, so use our custom threadpool.
243 m_threadpool.execute(task);
244 } else {
245 task.run();
246 }
247 }
248
249 public class Client {
250 final Ensure m_step;
251 volatile int m_dependencies;
252 volatile Dictionary m_conf;
253
254 public Client(Ensure step) {
255 m_step = step;
256 }
257
258 public void updated(Dictionary conf) throws ConfigurationException {
Pierre De Ropc8295c22015-06-04 10:15:35 +0000259 if (conf != null) {
260 try {
Pierre De Rop3a00a212015-03-01 09:27:46 +0000261 Assert.assertEquals("bar", conf.get("foo"));
Pierre De Ropc8295c22015-06-04 10:15:35 +0000262 m_conf = conf;
Pierre De Rop3a00a212015-03-01 09:27:46 +0000263 m_step.step(1);
Pierre De Ropc8295c22015-06-04 10:15:35 +0000264 } catch (Throwable t) {
265 m_step.throwable(t);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000266 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000267 }
268 }
269
270 void add(Dep d) {
271 Assert.assertNotNull(d);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000272 m_dependencies ++;
Pierre De Ropc8295c22015-06-04 10:15:35 +0000273 m_step.step();
Pierre De Rop3a00a212015-03-01 09:27:46 +0000274 }
275
276 void remove(Dep d) {
277 Assert.assertNotNull(d);
Pierre De Rop3a00a212015-03-01 09:27:46 +0000278 m_dependencies --;
Pierre De Ropc8295c22015-06-04 10:15:35 +0000279 m_step.step();
Pierre De Rop3a00a212015-03-01 09:27:46 +0000280 }
281
282 void start() {
283 m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */);
284 }
285
286 void stop() {
287 m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */);
288 }
289
290 void destroy() {
291 m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */ + 1 /* destroy */);
292 }
293
294 int getDependencies() {
295 return m_dependencies;
296 }
297
298 Dictionary getConfiguration() {
299 return m_conf;
300 }
301 }
302}