[rfc][icedtea-web] CacheEntry Locking Fix
Jiri Vanek
jvanek at redhat.com
Wed Aug 20 06:50:56 UTC 2014
>
> Thanks for the review :D
Sorry:( Not in own skin for some time;(
>
>> >Two nits
>> > - you should always unlock in finally clause.
> I've searched through the code and moved all applicable unlocks into finally clause.
>
>> > - the unittests - the tests you had in previouspatch (the ignored two) are
>> > not applicable?
>> > - if so,please, add
>> > - the unittest testing the locking feature is actually missing
>> >
>> >
>> >something like start thread one, lock and write write write. Start thread two
>> >and try to read
>> >(should be ok) start thread three and try to write - shouldnot write a single
>> >bite. Pelase add those
>> >teo tests.
> I've added two tests, one for testing Multiple Writes While Locked and one for Reading While Locked.
>
>
Still few nits.:
You added one final clausule - are those all usages of unlock? That does not mean all usages needs
to be in finally block, but .... they probably should
> Regards,
>
>> >
>> >
>> >J.
>> >
>> >Please add those two tests.
>> >
>> >
>> >
>> >
> -- Jie Kang
>
>
> itw-cache-lock-2.patch
>
>
There are some (actualy a lots) format-only changes (eg imports or similar) Please do your best t5o
backport those to 1.5.
> }
tion {
> if (this.processLock != null) {
> return;
> }
> @@ -140,7 +153,7 @@
> * Unlock access to the file. Lock is reentrant.
> */
> public void unlock() throws IOException {
> - if (JNLPRuntime.isWindows()) {
> + if (JNLPRuntime.isWindows() || !this.threadLock.isHeldByCurrentThread()) {
This is suspicious. Why so?
This locking have to work in process-way not only thread way. Otherwise it do not have sense.
> return;
> }
> boolean releaseProcessLock = (this.threadLock.getHoldCount() == 1);
> @@ -163,4 +176,8 @@
> this.threadLock.unlock();
> }
> }
> +
> + public boolean isHeldByCurrentThread() {
> + return this.threadLock.isHeldByCurrentThread();
> + }
> }
> \ No newline at end of file
> diff --git a/tests/netx/unit/net/sourceforge/jnlp/cache/CacheEntryTest.java b/tests/netx/unit/net/sourceforge/jnlp/cache/CacheEntryTest.java
> --- a/tests/netx/unit/net/sourceforge/jnlp/cache/CacheEntryTest.java
> +++ b/tests/netx/unit/net/sourceforge/jnlp/cache/CacheEntryTest.java
Uf from the patch it is not obvious but are all those tests writing to "sharp and used" cache file?
They should not.
> @@ -41,21 +41,25 @@
> import static org.junit.Assert.assertFalse;
> import static org.junit.Assert.assertTrue;
>
> +import java.io.ByteArrayOutputStream;
> import java.io.File;
> import java.io.IOException;
> +import java.io.PrintStream;
> import java.net.MalformedURLException;
> import java.net.URL;
> import java.nio.file.Files;
> +import java.util.concurrent.ExecutorService;
> +import java.util.concurrent.Executors;
> +
> +import org.junit.Before;
> +import org.junit.Test;
>
> import net.sourceforge.jnlp.Version;
> import net.sourceforge.jnlp.util.PropertiesFile;
>
> -import org.junit.Before;
> -import org.junit.Test;
> -
> public class CacheEntryTest {
>
> - /** A custom subclass that allows supplying a predefined cache file */
> + /** A custom subclass that allows supplying num predefined cache file */
> static class TestCacheEntry extends CacheEntry {
> private File cacheFile;
> public TestCacheEntry(URL location, Version version, File cacheFile) {
> @@ -79,10 +83,20 @@
> private URL url;
> private Version version;
>
> + private ByteArrayOutputStream baos;
> + private PrintStream out;
> + private ExecutorService executorService;
> + Object listener = new Object();
> +
> + int num = 0;
> +
> @Before
> public void setUp() throws MalformedURLException {
> url = new URL("http://example.com/example.jar");
> version = new Version("1.0");
> + baos = new ByteArrayOutputStream();
> + out = new PrintStream(baos);
> + executorService = Executors.newSingleThreadExecutor();
> }
>
> @Test
> @@ -208,4 +222,126 @@
> return cachedFile;
> }
>
> + @Test
> + public void testLock() throws IOException {
> + CacheEntry entry = new CacheEntry(url, version);
> + entry.lock();
> + assertTrue(entry.isHeldByCurrentThread());
> + entry.unlock();
> + }
> +
> + @Test
> + public void testUnlock() throws IOException {
> + CacheEntry entry = new CacheEntry(url, version);
> + entry.lock();
> + entry.unlock();
> + assertTrue(!entry.isHeldByCurrentThread());
> + }
> +
> + @Test
> + public void testStoreFailsWithoutLock() throws IOException {
> + CacheEntry entry = new CacheEntry(url, version);
> + long num = 10;
> + entry.setLastModified(num);
> + assertTrue(!entry.store());
> + }
> +
> + @Test
> + public void testStoreWorksWithLocK() throws IOException {
> + CacheEntry entry = new CacheEntry(url, version);
> + long num = 10;
> + entry.setLastModified(num);
> + entry.lock();
> + assertTrue(entry.store());
> + entry.unlock();
> + }
> +
> + @Test
> + public void testMultithreadLockPreventsWrite() throws IOException, InterruptedException {
> + Thread a = new Thread(new WriteWorker(10));
> + a.start();
> + Thread b = new Thread(new WriteWorker(5));
> + b.start();
> +
> + Thread.sleep(2000l);
> +
> + synchronized (listener) {
> + num = 1;
> + listener.notifyAll();
> + }
> +
> + String out = baos.toString();
> + assertTrue(out.contains(":10:true\n:5:false"));
> + }
> +
> + @Test
> + public void testMultithreadLockAllowsRead() throws IOException, InterruptedException {
> + Thread a = new Thread(new WriteWorker(10));
> + a.start();
> +
> + Thread.sleep(2000l);
> +
> + Thread b = new Thread(new ReadWorker());
> + b.start();
> +
> + Thread.sleep(2000l);
> +
> + synchronized (listener) {
> + num = 1;
> + listener.notifyAll();
> + }
> +
> + String out = baos.toString();
> +
> + assertTrue(out.contains(":10:true\n:read:10"));
> + }
> +
> + private class WriteWorker implements Runnable {
> + long input;
> + public WriteWorker(long input) {
> + this.input = input;
> + }
> + @Override
> + public void run() {
> + CacheEntry entry = new CacheEntry(url, version);
> + try {
> + entry.tryLock();
> + entry.setLastModified(input);
> + boolean result = entry.store();
> + executorService.execute(new WriteRunnable(":" + input + ":" + result));
> + while (num == 0) {
> + synchronized (listener) {
> + listener.wait();
> + }
> + }
> + } catch (Exception e) {
> + e.printStackTrace();
> + } finally {
> + entry.unlock();
> + }
> + }
> + }
> +
> + private class WriteRunnable implements Runnable {
> + private String msg;
> + public WriteRunnable(String msg) {
> + this.msg = msg;
> + }
> +
> + @Override
> + public void run() {
> + out.println(msg);
> + out.flush();
> + }
> + }
> +
> + private class ReadWorker implements Runnable {
> +
> + @Override
> + public void run() {
> + CacheEntry entry = new CacheEntry(url, version);
> + long lastModified = entry.getLastModified();
> + executorService.execute(new WriteRunnable(":read:" + lastModified));
> + }
> + }
> }
>
Actually.... Are those two tests failing without the patch itsef?
J.
More information about the distro-pkg-dev
mailing list