Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 19 | package org.apache.felix.dm.itest.api; |
| 20 | |
| 21 | import java.io.IOException; |
| 22 | import java.util.ArrayList; |
| 23 | import java.util.Dictionary; |
| 24 | import java.util.Hashtable; |
| 25 | import java.util.List; |
| 26 | import java.util.concurrent.ExecutorService; |
| 27 | import java.util.concurrent.Executors; |
| 28 | import java.util.concurrent.TimeUnit; |
| 29 | |
| 30 | import org.junit.Assert; |
| 31 | |
| 32 | import org.apache.felix.dm.Component; |
| 33 | import org.apache.felix.dm.ConfigurationDependency; |
| 34 | import org.apache.felix.dm.ServiceDependency; |
| 35 | import org.apache.felix.dm.itest.util.Ensure; |
| 36 | import org.apache.felix.dm.itest.util.TestBase; |
| 37 | import org.osgi.service.cm.Configuration; |
| 38 | import org.osgi.service.cm.ConfigurationAdmin; |
| 39 | import 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"}) |
| 48 | public 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 Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 224 | step.waitForStep(expectedStep, STEP_WAIT); |
| 225 | step.ensure(); |
| 226 | Assert.assertEquals(0, clientImpl.getDependencies()); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 227 | |
| 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 Rop | c8295c2 | 2015-06-04 10:15:35 +0000 | [diff] [blame^] | 259 | if (conf != null) { |
| 260 | try { |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 261 | Assert.assertEquals("bar", conf.get("foo")); |
Pierre De Rop | c8295c2 | 2015-06-04 10:15:35 +0000 | [diff] [blame^] | 262 | m_conf = conf; |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 263 | m_step.step(1); |
Pierre De Rop | c8295c2 | 2015-06-04 10:15:35 +0000 | [diff] [blame^] | 264 | } catch (Throwable t) { |
| 265 | m_step.throwable(t); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 266 | } |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 267 | } |
| 268 | } |
| 269 | |
| 270 | void add(Dep d) { |
| 271 | Assert.assertNotNull(d); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 272 | m_dependencies ++; |
Pierre De Rop | c8295c2 | 2015-06-04 10:15:35 +0000 | [diff] [blame^] | 273 | m_step.step(); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 274 | } |
| 275 | |
| 276 | void remove(Dep d) { |
| 277 | Assert.assertNotNull(d); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 278 | m_dependencies --; |
Pierre De Rop | c8295c2 | 2015-06-04 10:15:35 +0000 | [diff] [blame^] | 279 | m_step.step(); |
Pierre De Rop | 3a00a21 | 2015-03-01 09:27:46 +0000 | [diff] [blame] | 280 | } |
| 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 | } |