001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tctest;
006:
007: import org.apache.commons.io.FileUtils;
008:
009: import com.tc.config.schema.builder.InstrumentedClassConfigBuilder;
010: import com.tc.config.schema.builder.LockConfigBuilder;
011: import com.tc.config.schema.builder.RootConfigBuilder;
012: import com.tc.config.schema.test.InstrumentedClassConfigBuilderImpl;
013: import com.tc.config.schema.test.LockConfigBuilderImpl;
014: import com.tc.config.schema.test.RootConfigBuilderImpl;
015: import com.tc.config.schema.test.TerracottaConfigBuilder;
016: import com.tc.object.bytecode.hook.impl.ClassProcessorHelper;
017: import com.tc.object.config.ConfigVisitor;
018: import com.tc.object.config.DSOClientConfigHelper;
019: import com.tc.object.config.TransparencyClassSpec;
020: import com.tc.object.loaders.IsolationClassLoader;
021: import com.tc.object.loaders.NamedClassLoader;
022: import com.tc.objectserver.control.ExtraL1ProcessControl;
023: import com.tc.simulator.app.ApplicationConfig;
024: import com.tc.simulator.listener.ListenerProvider;
025: import com.tc.text.Banner;
026: import com.tc.util.Assert;
027: import com.tc.util.concurrent.ThreadUtil;
028:
029: import java.io.File;
030: import java.net.URL;
031: import java.net.URLClassLoader;
032: import java.util.ArrayList;
033: import java.util.Collection;
034: import java.util.Iterator;
035: import java.util.LinkedList;
036: import java.util.List;
037:
038: public class NewObjectMemoryManagerRaceTest extends
039: ServerCrashingTestBase {
040: private static final long TEST_DURATION = 2 * 60 * 1000;
041: private static final long END = System.currentTimeMillis()
042: + TEST_DURATION;
043:
044: public NewObjectMemoryManagerRaceTest() {
045: super (1); // only need 1 node
046: }
047:
048: protected Class getApplicationClass() {
049: return NewObjectMemoryManagerTestApp.class;
050: }
051:
052: public static boolean shouldEnd() {
053: return Shared.isEnd() || System.currentTimeMillis() > END;
054: }
055:
056: protected void createConfig(TerracottaConfigBuilder cb) {
057: String sharedClassName = Shared.class.getName();
058:
059: LockConfigBuilder lock = new LockConfigBuilderImpl(
060: LockConfigBuilder.TAG_AUTO_LOCK);
061: lock.setMethodExpression("* " + sharedClassName + ".put()");
062: lock.setLockLevel(LockConfigBuilder.LEVEL_WRITE);
063:
064: LockConfigBuilder lock2 = new LockConfigBuilderImpl(
065: LockConfigBuilder.TAG_AUTO_LOCK);
066: lock2.setMethodExpression("* " + sharedClassName + ".size()");
067: lock2.setLockLevel(LockConfigBuilder.LEVEL_READ);
068:
069: cb.getApplication().getDSO().setLocks(
070: new LockConfigBuilder[] { lock, lock2 });
071:
072: RootConfigBuilder root = new RootConfigBuilderImpl();
073: root.setFieldName(sharedClassName + ".queue");
074: root.setRootName("queue");
075:
076: RootConfigBuilder root2 = new RootConfigBuilderImpl();
077: root2.setFieldName(sharedClassName + ".end");
078: root2.setRootName("end");
079:
080: cb.getApplication().getDSO().setRoots(
081: new RootConfigBuilder[] { root, root2 });
082:
083: InstrumentedClassConfigBuilder instrumented = new InstrumentedClassConfigBuilderImpl();
084: instrumented.setClassExpression(Foo.class.getName());
085:
086: cb.getApplication().getDSO().setInstrumentedClasses(
087: new InstrumentedClassConfigBuilder[] { instrumented });
088: }
089:
090: private static class Shared {
091: // roots
092: private static final LinkedList queue = new LinkedList();
093: private static boolean end = false;
094:
095: private static final int BATCH = 5000;
096: private static int putCount = 0;
097:
098: static Collection take() {
099: Collection rv = new ArrayList();
100: synchronized (queue) {
101: while (!queue.isEmpty()) {
102: rv.add(queue.removeFirst());
103: }
104: }
105: return rv;
106: }
107:
108: static int size() {
109: synchronized (queue) {
110: return queue.size();
111: }
112: }
113:
114: static void put() {
115: while (size() > 0) {
116: ThreadUtil.reallySleep(250);
117: }
118:
119: synchronized (queue) {
120: for (int i = 0; i < BATCH; i++) {
121: queue.addLast(new Foo());
122: putCount++;
123: }
124: }
125:
126: System.err.println("put " + putCount);
127: }
128:
129: static boolean isEnd() {
130: return end;
131: }
132:
133: static void end() {
134: end = true;
135: }
136:
137: }
138:
139: /**
140: * The bug we are reproducing is a data race where the fields in a newly shared object (which are not null) are being
141: * nulled by the client side memory manager before the object is dehydrated, causing the object to have null fields
142: * when it is shared. This causes other clients to see the object as having null fields instead of the actual data.
143: * This is due to a data race in TCChangeBufferImpl.writeTo() where an object is marked as non-NEW too early (before
144: * it's been shared).
145: */
146: public static class NewObjectMemoryManagerTestApp extends
147: ServerCrashingAppBase {
148:
149: public NewObjectMemoryManagerTestApp(String appId,
150: ApplicationConfig cfg, ListenerProvider listenerProvider) {
151: super (appId, cfg, listenerProvider);
152: }
153:
154: public static void visitL1DSOConfig(ConfigVisitor visitor,
155: DSOClientConfigHelper config) {
156: config.getOrCreateSpec(Foo.class.getName());
157: config.addIncludePattern(Foo.class.getName());
158:
159: TransparencyClassSpec spec = config
160: .getOrCreateSpec(Shared.class.getName());
161: spec.addRoot("queue", "queue");
162: spec.addRoot("end", "end");
163: config.addWriteAutolock("* " + Shared.class.getName()
164: + ".take()");
165: }
166:
167: public void runTest() throws Throwable {
168: Thread t = new Thread() {
169: public void run() {
170: createNewObjects();
171: }
172: };
173: t.start();
174:
175: observeNewObjects();
176:
177: t.join(45000);
178:
179: if (t.isAlive()) {
180: Banner.warnBanner("Spawned client still alive");
181: }
182: }
183:
184: /**
185: * One client will create lots of objects that have non-null fields and add them to a shared queue.
186: *
187: * @throws InterruptedException
188: */
189: void createNewObjects() {
190: try {
191: File workingDir = new File(
192: getConfigFileDirectoryPath(), "external-client");
193: FileUtils.forceMkdir(workingDir);
194:
195: // spawn the new node with very aggressive L1 cache settings
196: List jvmArgs = new ArrayList();
197: jvmArgs
198: .add("-Dcom.tc.l1.cachemanager.logging.enabled=true");
199: jvmArgs.add("-Dcom.tc.l1.cachemanager.leastCount=1");
200: jvmArgs
201: .add("-Dcom.tc.l1.cachemanager.percentageToEvict=99");
202: jvmArgs.add("-Dcom.tc.l1.cachemanager.sleepInterval=1");
203: jvmArgs
204: .add("-Dcom.tc.l1.cachemanager.criticalThreshold=2");
205: jvmArgs.add("-Dcom.tc.l1.cachemanager.threshold=1");
206:
207: ExtraL1ProcessControl client = new ExtraL1ProcessControl(
208: getHostName(), getPort(), External.class,
209: getConfigFilePath(), new String[] {},
210: workingDir, jvmArgs);
211: client.setJavaHome(new File(System
212: .getProperty("java.home")));
213: client.start();
214: int exitCode = client.waitFor();
215: if (exitCode != 0) {
216: String errorMsg = "Client existed Abnormally. Exit code = "
217: + exitCode;
218: System.err.println(errorMsg);
219: throw new AssertionError(errorMsg);
220: }
221: } catch (Throwable t) {
222: notifyError(t);
223: }
224: }
225:
226: /**
227: * Other clients will look at those shared objects in the queue and check whether the fields are null or not. If
228: * things are working as designed, the fields should never be null as they are never null when they are added to the
229: * shared queue.
230: */
231: private void observeNewObjects() throws Throwable {
232: int count = 0;
233:
234: while (true) {
235: Collection taken = Shared.take();
236: if (taken.isEmpty()) {
237: if (Shared.isEnd()) {
238: return;
239: } else {
240: ThreadUtil.reallySleep(250);
241: continue;
242: }
243: }
244:
245: for (Iterator i = taken.iterator(); i.hasNext();) {
246: Foo foo = (Foo) i.next();
247:
248: if ((++count % 500) == 0) {
249: System.err.println("eval'd " + count);
250: }
251:
252: try {
253: foo.validate();
254: } catch (Throwable t) {
255: Shared.end();
256: throw t;
257: }
258: }
259: }
260: }
261: }
262:
263: private static class Foo {
264: private final Object f1 = this ;
265: private final Object f2 = this ;
266: private final Object f3 = this ;
267: private final Object f4 = this ;
268: private final Object f5 = this ;
269: private final Object f6 = this ;
270: private final Object f7 = this ;
271: private final Object f8 = this ;
272: private final Object f9 = this ;
273: private final Object f10 = this ;
274: private final Object f11 = this ;
275: private final Object f12 = this ;
276: private final Object f13 = this ;
277: private final Object f14 = this ;
278: private final Object f15 = this ;
279: private final Object f16 = this ;
280: private final Object f17 = this ;
281: private final Object f18 = this ;
282: private final Object f19 = this ;
283: private final Object f20 = this ;
284: private final Object f21 = this ;
285: private final Object f22 = this ;
286: private final Object f23 = this ;
287: private final Object f24 = this ;
288: private final Object f25 = this ;
289: private final Object f26 = this ;
290: private final Object f27 = this ;
291: private final Object f28 = this ;
292: private final Object f29 = this ;
293: private final Object f30 = this ;
294:
295: void validate() {
296: Assert.assertNotNull(f1);
297: Assert.assertNotNull(f2);
298: Assert.assertNotNull(f3);
299: Assert.assertNotNull(f4);
300: Assert.assertNotNull(f5);
301: Assert.assertNotNull(f6);
302: Assert.assertNotNull(f7);
303: Assert.assertNotNull(f8);
304: Assert.assertNotNull(f9);
305: Assert.assertNotNull(f10);
306: Assert.assertNotNull(f11);
307: Assert.assertNotNull(f12);
308: Assert.assertNotNull(f13);
309: Assert.assertNotNull(f14);
310: Assert.assertNotNull(f15);
311: Assert.assertNotNull(f16);
312: Assert.assertNotNull(f17);
313: Assert.assertNotNull(f18);
314: Assert.assertNotNull(f19);
315: Assert.assertNotNull(f20);
316: Assert.assertNotNull(f21);
317: Assert.assertNotNull(f22);
318: Assert.assertNotNull(f23);
319: Assert.assertNotNull(f24);
320: Assert.assertNotNull(f25);
321: Assert.assertNotNull(f26);
322: Assert.assertNotNull(f27);
323: Assert.assertNotNull(f28);
324: Assert.assertNotNull(f29);
325: Assert.assertNotNull(f30);
326: }
327: }
328:
329: public static class External implements Runnable {
330: public static void main(String[] args) throws Exception {
331: try {
332: // Need to pretend like we are sharing classes from a loader named the same as
333: // the test framework's IsolationClassLaoder
334: Loader loader = Loader.create();
335: ((NamedClassLoader) loader)
336: .__tc_setClassLoaderName(IsolationClassLoader
337: .loaderName());
338: ClassProcessorHelper
339: .registerGlobalLoader((NamedClassLoader) loader);
340:
341: Thread.currentThread().setContextClassLoader(loader);
342: Runnable r = (Runnable) loader.loadClass(
343: External.class.getName()).newInstance();
344: r.run();
345: } finally {
346: Shared.end();
347: }
348: }
349:
350: public void run() {
351: while (!NewObjectMemoryManagerRaceTest.shouldEnd()) {
352: Shared.put();
353: }
354: }
355:
356: static class Loader extends URLClassLoader {
357:
358: public Loader(URL[] urls, ClassLoader parent) {
359: super (urls, parent);
360: }
361:
362: static Loader create() {
363: URL[] urls = ((URLClassLoader) ClassLoader
364: .getSystemClassLoader()).getURLs();
365: return new Loader(urls, null);
366: }
367: }
368:
369: }
370:
371: }
|