From d8b0b504894255543e68f074b30051a91f8a1154 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 30 Aug 2013 14:38:48 +0300 Subject: Always unlock the same session instance that was locked (#12481) Change-Id: I15ff1177d827bc8fee9f8f723f9a288b0c3aea71 --- server/src/com/vaadin/server/AbstractClientConnector.java | 5 +++-- server/src/com/vaadin/server/FileDownloader.java | 6 ++++-- server/src/com/vaadin/ui/AbstractMedia.java | 6 ++++-- 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'server/src') diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java index 91a9e41522..a73ca3d985 100644 --- a/server/src/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/com/vaadin/server/AbstractClientConnector.java @@ -625,7 +625,8 @@ public abstract class AbstractClientConnector implements ClientConnector, String[] parts = path.split("/", 2); String key = parts[0]; - getSession().lock(); + VaadinSession session = getSession(); + session.lock(); try { ConnectorResource resource = (ConnectorResource) getResource(key); if (resource == null) { @@ -633,7 +634,7 @@ public abstract class AbstractClientConnector implements ClientConnector, } stream = resource.getStream(); } finally { - getSession().unlock(); + session.unlock(); } stream.writeResponse(request, response); return true; diff --git a/server/src/com/vaadin/server/FileDownloader.java b/server/src/com/vaadin/server/FileDownloader.java index 9b49ad8edd..bd7d9caafd 100644 --- a/server/src/com/vaadin/server/FileDownloader.java +++ b/server/src/com/vaadin/server/FileDownloader.java @@ -129,7 +129,9 @@ public class FileDownloader extends AbstractExtension { // Ignore if it isn't for us return false; } - getSession().lock(); + VaadinSession session = getSession(); + + session.lock(); DownloadStream stream; try { @@ -151,7 +153,7 @@ public class FileDownloader extends AbstractExtension { stream.setContentType("application/octet-stream;charset=UTF-8"); } } finally { - getSession().unlock(); + session.unlock(); } stream.writeResponse(request, response); return true; diff --git a/server/src/com/vaadin/ui/AbstractMedia.java b/server/src/com/vaadin/ui/AbstractMedia.java index 97947b568d..d7d593c29e 100644 --- a/server/src/com/vaadin/ui/AbstractMedia.java +++ b/server/src/com/vaadin/ui/AbstractMedia.java @@ -30,6 +30,7 @@ import com.vaadin.server.Resource; import com.vaadin.server.ResourceReference; import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinResponse; +import com.vaadin.server.VaadinSession; import com.vaadin.shared.communication.URLReference; import com.vaadin.shared.ui.AbstractMediaState; import com.vaadin.shared.ui.MediaControl; @@ -90,7 +91,8 @@ public abstract class AbstractMedia extends AbstractComponent { DownloadStream stream; - getSession().lock(); + VaadinSession session = getSession(); + session.lock(); try { List sources = getState().sources; @@ -108,7 +110,7 @@ public abstract class AbstractMedia extends AbstractComponent { .getResource(reference); stream = resource.getStream(); } finally { - getSession().unlock(); + session.unlock(); } stream.writeResponse(request, response); -- cgit v1.2.3 From e6af0f0a5a1333d06e18d0149d44231a5f8e654d Mon Sep 17 00:00:00 2001 From: Jonatan Kronqvist Date: Wed, 28 Aug 2013 13:53:45 +0300 Subject: Avoid leaking memory from inherited ThreadLocales. Fixes #12401 The issue is fixed by changing the normal HashMap inside the inheritable thread local to a map implementation holding only weak references to the values (WeakValueMap). Also included is a test UI that starts threads, which run until the JVM is quit. This along with VisualVM was used to reproduce the issue and verify the fix. Change-Id: I116cc4e56e8a19c3b770abab6b18b9e262f4dafa --- server/src/com/vaadin/util/CurrentInstance.java | 2 +- server/src/com/vaadin/util/WeakValueMap.java | 228 +++++++++++++++++++++ .../tests/performance/ThreadMemoryLeaksTest.java | 57 ++++++ 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 server/src/com/vaadin/util/WeakValueMap.java create mode 100644 uitest/src/com/vaadin/tests/performance/ThreadMemoryLeaksTest.java (limited to 'server/src') diff --git a/server/src/com/vaadin/util/CurrentInstance.java b/server/src/com/vaadin/util/CurrentInstance.java index b97bab3d8a..a1c543117d 100644 --- a/server/src/com/vaadin/util/CurrentInstance.java +++ b/server/src/com/vaadin/util/CurrentInstance.java @@ -60,7 +60,7 @@ public class CurrentInstance implements Serializable { return null; } - Map, CurrentInstance> value = new HashMap, CurrentInstance>(); + Map, CurrentInstance> value = new WeakValueMap, CurrentInstance>(); // Copy all inheritable values to child map for (Entry, CurrentInstance> e : parentValue.entrySet()) { diff --git a/server/src/com/vaadin/util/WeakValueMap.java b/server/src/com/vaadin/util/WeakValueMap.java new file mode 100644 index 0000000000..b5ac12ce91 --- /dev/null +++ b/server/src/com/vaadin/util/WeakValueMap.java @@ -0,0 +1,228 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A Map holding weak references to its values. It is internally backed by a + * normal HashMap and all values are stored as WeakReferences. Garbage collected + * entries are removed when touched. + * + * @author Vaadin Ltd + * @since 7.1.4 + */ +public class WeakValueMap implements Map { + + /** + * This class holds a weak reference to the value and a strong reference to + * the key for efficient removal of stale values. + */ + private static class WeakValueReference extends WeakReference { + private final K key; + + WeakValueReference(K key, V value, ReferenceQueue refQueue) { + super(value, refQueue); + this.key = key; + } + + K getKey() { + return key; + } + } + + private final HashMap> backingMap; + private final ReferenceQueue refQueue; + + /** + * Constructs a new WeakValueMap, where all values are stored as weak + * references. + */ + public WeakValueMap() { + backingMap = new HashMap>(); + refQueue = new ReferenceQueue(); + } + + /** + * {@inheritDoc} + */ + @Override + public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("key cannot be null"); + } + if (value == null) { + throw new NullPointerException("value cannot be null"); + } + removeStaleEntries(); + backingMap.put(key, new WeakValueReference(key, value, refQueue)); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public V remove(Object o) { + removeStaleEntries(); + WeakReference value = backingMap.remove(o); + return value == null ? null : value.get(); + } + + /** + * {@inheritDoc} + */ + @Override + public void putAll(Map map) { + if (map != null) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() { + backingMap.clear(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set keySet() { + removeStaleEntries(); + return backingMap.keySet(); + } + + /** + * {@inheritDoc} + */ + @Override + public V get(Object o) { + removeStaleEntries(); + WeakReference weakValue = backingMap.get(o); + if (weakValue != null) { + return weakValue.get(); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int size() { + removeStaleEntries(); + return backingMap.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + removeStaleEntries(); + return backingMap.isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsKey(Object o) { + removeStaleEntries(); + return backingMap.containsKey(o); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsValue(Object o) { + removeStaleEntries(); + for (V value : values()) { + if (o.equals(value)) { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Collection values() { + removeStaleEntries(); + Collection values = new HashSet(); + for (WeakReference weakValue : backingMap.values()) { + V value = weakValue.get(); + if (value != null) { + // null values have been GC'd, which may happen long before + // anything is enqueued in the ReferenceQueue. + values.add(value); + } + } + return values; + } + + /** + * {@inheritDoc} + */ + @Override + public Set> entrySet() { + removeStaleEntries(); + Set> entrySet = new HashSet>(); + for (Entry> entry : backingMap.entrySet()) { + V value = entry.getValue().get(); + if (value != null) { + // null values have been GC'd, which may happen long before + // anything is enqueued in the ReferenceQueue. + entrySet.add(new AbstractMap.SimpleEntry(entry.getKey(), + value)); + } + } + return entrySet; + } + + /** + * Cleans up stale entries by polling the ReferenceQueue. + *

+ * Depending on the GC implementation and strategy, the ReferenceQueue is + * not necessarily notified immediately when a reference is garbage + * collected, but it will eventually be. + */ + private void removeStaleEntries() { + Reference ref; + while ((ref = refQueue.poll()) != null) { + Object key = ((WeakValueReference) ref).getKey(); + backingMap.remove(key); + } + } +} diff --git a/uitest/src/com/vaadin/tests/performance/ThreadMemoryLeaksTest.java b/uitest/src/com/vaadin/tests/performance/ThreadMemoryLeaksTest.java new file mode 100644 index 0000000000..5ffc7141af --- /dev/null +++ b/uitest/src/com/vaadin/tests/performance/ThreadMemoryLeaksTest.java @@ -0,0 +1,57 @@ +package com.vaadin.tests.performance; + +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Label; + +public class ThreadMemoryLeaksTest extends AbstractTestUI { + + public static class Worker { + long value = 0; + private TimerTask task = new TimerTask() { + @Override + public void run() { + value++; + } + }; + private final Timer timer = new Timer(true); + + public Worker() { + timer.scheduleAtFixedRate(task, new Date(), 1000); + } + } + + int workers = 0; + Label label; + + @Override + protected void setup(VaadinRequest request) { + label = new Label(String.format("%d workers", workers)); + addComponent(label); + addComponent(new Button("Add worker", new Button.ClickListener() { + @Override + public void buttonClick(Button.ClickEvent event) { + new Worker(); + workers++; + label.setValue(String.format("%d workers", workers)); + } + })); + } + + @Override + protected String getTestDescription() { + return "Inherited ThreadLocals should not leak memory. Clicking the " + + "button starts a new thread, after which memory consumption " + + "can be checked with visualvm"; + } + + @Override + protected Integer getTicketNumber() { + return 12401; + } +} -- cgit v1.2.3 From a24d391349354579a83ab2f6b50e9552fbe2566a Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Tue, 20 Aug 2013 15:47:54 +0300 Subject: Table ignores Container updates while painting (#12258) Vaadin threw an IllegalStateException if a Container was updated while Table was being painted. SQLContainer was known to invalidate its cached size during a Table repaint, resulting in an ItemSetChangeEvent. This fix has been copied over from how ComboBox handles this situation. Change-Id: I04af71a5ea3844da245cb9e31ada4a30ff704619 --- server/src/com/vaadin/ui/Table.java | 19 +++ .../table/ContainerSizeChangeDuringTablePaint.html | 47 ++++++ .../table/ContainerSizeChangeDuringTablePaint.java | 163 +++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.html create mode 100644 uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.java (limited to 'server/src') diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java index 3507e6b0a5..bd2b7828de 100644 --- a/server/src/com/vaadin/ui/Table.java +++ b/server/src/com/vaadin/ui/Table.java @@ -564,6 +564,8 @@ public class Table extends AbstractSelect implements Action.Container, private List exceptionsDuringCachePopulation = new ArrayList(); + private boolean isBeingPainted; + /* Table constructors */ /** @@ -3166,6 +3168,15 @@ public class Table extends AbstractSelect implements Action.Container, @Override public void paintContent(PaintTarget target) throws PaintException { + isBeingPainted = true; + try { + doPaintContent(target); + } finally { + isBeingPainted = false; + } + } + + private void doPaintContent(PaintTarget target) throws PaintException { /* * Body actions - Actions which has the target null and can be invoked * by right clicking on the table body. @@ -4394,6 +4405,10 @@ public class Table extends AbstractSelect implements Action.Container, @Override public void containerItemSetChange(Container.ItemSetChangeEvent event) { + if (isBeingPainted) { + return; + } + super.containerItemSetChange(event); // super method clears the key map, must inform client about this to @@ -4416,6 +4431,10 @@ public class Table extends AbstractSelect implements Action.Container, @Override public void containerPropertySetChange( Container.PropertySetChangeEvent event) { + if (isBeingPainted) { + return; + } + disableContentRefreshing(); super.containerPropertySetChange(event); diff --git a/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.html b/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.html new file mode 100644 index 0000000000..236f184a41 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.html @@ -0,0 +1,47 @@ + + + + + + +ContainerSizeChangeDuringTablePaint + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ContainerSizeChangeDuringTablePaint
open/run/com.vaadin.tests.components.table.ContainerSizeChangeDuringTablePaint?restartApplication
verifyTextPresentAdd an item and also trigger an ItemSetChangeEvent in Container during next Table paint
clickvaadin=runcomvaadintestscomponentstableContainerSizeChangeDuringTablePaint::PID_SaddRow/domChild[0]/domChild[0]
verifyTextPresentEvent was fired successfully.
verifyTextPresentNew
verifyTextPresentRow
+ + diff --git a/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.java b/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.java new file mode 100644 index 0000000000..0f385176bf --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/ContainerSizeChangeDuringTablePaint.java @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.tests.components.table; + +import java.util.Iterator; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.server.PaintException; +import com.vaadin.server.PaintTarget; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Table; + +public class ContainerSizeChangeDuringTablePaint extends AbstractTestUI { + + /** + * A test {@link Table} that simply acts a hook for when Vaadin starts + * painting the Table. + */ + private static class WobblyTable extends Table { + /** + * A flag for the container to know when it should change the size. + */ + boolean isBeingPainted; + + public WobblyTable(String caption, Container dataSource) { + super(caption, dataSource); + } + + @Override + public void paintContent(PaintTarget target) throws PaintException { + isBeingPainted = true; + try { + super.paintContent(target); + } finally { + isBeingPainted = false; + } + } + } + + /** + * A {@link Container} that can change its size while its + * {@link WobblyTable} is being painted. + */ + private static class WobblyContainer extends IndexedContainer { + private WobblyTable table = null; + private boolean shouldSabotageNextPaint = false; + + public void setWobblyTable(WobblyTable table) { + this.table = table; + } + + @Override + public int size() { + if (table != null && table.isBeingPainted + && shouldSabotageNextPaint) { + try { + System.out.print("Firing item set change " + + "event during Table paint... "); + fireItemSetChange(); + System.out.println("Success!"); + } finally { + shouldSabotageNextPaint = false; + } + } + + return super.size(); + } + + public void sabotageNextPaint() { + shouldSabotageNextPaint = true; + } + } + + private static final Object PROPERTY_1 = new Object(); + private static final Object PROPERTY_2 = new Object(); + private static final Object PROPERTY_3 = new Object(); + + @Override + protected void setup(VaadinRequest request) { + final WobblyContainer container = generateContainer(); + final WobblyTable table = createTable(container); + container.setWobblyTable(table); + + addComponent(table); + Button button = new Button( + "Add an item and also trigger an ItemSetChangeEvent in Container during next Table paint", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + + // we need this to simply trigger a table paint. + addItem(container, "A", "New", "Row"); + container.sabotageNextPaint(); + + event.getButton().setCaption( + "Event was fired successfully."); + } + }); + button.setId("addRow"); + addComponent(button); + } + + private static WobblyTable createTable(IndexedContainer container) { + WobblyTable t = new WobblyTable(null, container); + t.setColumnHeader(PROPERTY_1, "Property 1"); + t.setColumnHeader(PROPERTY_2, "Property 2"); + t.setColumnHeader(PROPERTY_3, "Property 3"); + t.setPageLength(container.size() + 1); + return t; + } + + private static WobblyContainer generateContainer() { + WobblyContainer c = new WobblyContainer(); + c.addContainerProperty(PROPERTY_1, String.class, null); + c.addContainerProperty(PROPERTY_2, String.class, null); + c.addContainerProperty(PROPERTY_3, String.class, null); + addItem(c, "Hello", "World", "!"); + return c; + } + + @SuppressWarnings("unchecked") + private static void addItem(Container c, Object... properties) { + Object itemId = c.addItem(); + Item item = c.getItem(itemId); + int i = 0; + Iterator propIter = c.getContainerPropertyIds().iterator(); + while (propIter.hasNext()) { + Object propertyId = propIter.next(); + item.getItemProperty(propertyId).setValue(properties[i]); + i++; + } + } + + @Override + protected String getTestDescription() { + return "Container changes during the painting cycle should not lead to an IllegalStateException"; + } + + @Override + protected Integer getTicketNumber() { + return 12258; + } + +} -- cgit v1.2.3 From cb8df753212908cb67aebd550d45ee8d8083ff89 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 9 Jul 2013 09:36:26 +0300 Subject: Add deprecation message to unused constant Change-Id: Ifcc4e021fa3843129f6501d6526e4d4f5c6930fb --- server/src/com/vaadin/server/Constants.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'server/src') diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index ab91ee021c..8c379abe06 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -146,6 +146,11 @@ public interface Constants { // Widget set parameter name static final String PARAMETER_WIDGETSET = "widgetset"; + /** + * @deprecated As of 7.1, this message is no longer used and might be + * removed from the code. + */ + @Deprecated static final String ERROR_NO_UI_FOUND = "No UIProvider returned a UI for the request."; static final String DEFAULT_THEME_NAME = "reindeer"; -- cgit v1.2.3 From 11cdf93fedc9e693468d25092afba8172ce8ebf0 Mon Sep 17 00:00:00 2001 From: Jonatan Kronqvist Date: Tue, 3 Sep 2013 09:53:10 +0300 Subject: Excludes WeakValueMap from the serializable test #12401 Also added a note to the WeakValueMap JavaDoc stating that it is not serializable. Change-Id: If342746ad7c7cac0db8bac4ba75236970cc4cd01 --- server/src/com/vaadin/util/WeakValueMap.java | 2 ++ server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java | 1 + 2 files changed, 3 insertions(+) (limited to 'server/src') diff --git a/server/src/com/vaadin/util/WeakValueMap.java b/server/src/com/vaadin/util/WeakValueMap.java index b5ac12ce91..1134594cba 100644 --- a/server/src/com/vaadin/util/WeakValueMap.java +++ b/server/src/com/vaadin/util/WeakValueMap.java @@ -30,6 +30,8 @@ import java.util.Set; * A Map holding weak references to its values. It is internally backed by a * normal HashMap and all values are stored as WeakReferences. Garbage collected * entries are removed when touched. + *

+ * Note this class is not serializable. * * @author Vaadin Ltd * @since 7.1.4 diff --git a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java index 2a7e456fcb..e5420b8921 100644 --- a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java +++ b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java @@ -65,6 +65,7 @@ public class TestClassesSerializable extends TestCase { "com\\.vaadin\\.util\\.ConnectorHelper", // "com\\.vaadin\\.server\\.VaadinSession\\$FutureAccess", // "com\\.vaadin\\.external\\..*", // + "com\\.vaadin\\.util\\.WeakValueMap.*", // }; /** -- cgit v1.2.3