Skip to content

Commit 550395b

Browse files
author
thomas.tom.mueller@gmail.com
committed
References to BLOB and CLOB objects now have a timeout. The configuration setting is LOB_TIMEOUT (default 5 minutes). This should avoid growing the database file if there are many queries that return BLOB or CLOB objects, and the database is not closed for a longer time.
git-svn-id: http://h2database.googlecode.com/svn/trunk@6116 e6896862-9d19-0410-bfb3-e9fa5d50e656
1 parent 4349d1c commit 550395b

7 files changed

Lines changed: 145 additions & 18 deletions

File tree

h2/src/main/org/h2/api/ErrorCode.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,13 @@ public class ErrorCode {
804804
*/
805805
public static final int VIEW_ALREADY_EXISTS_1 = 90038;
806806

807+
/**
808+
* The error with code <code>90039</code> is thrown when
809+
* trying to access a CLOB or BLOB object that timed out.
810+
* See the database setting LOB_TIMEOUT.
811+
*/
812+
public static final int LOB_CLOSED_ON_TIMEOUT_1 = 90039;
813+
807814
/**
808815
* The error with code <code>90040</code> is thrown when
809816
* a user that is not administrator tries to execute a statement
@@ -1907,7 +1914,7 @@ public class ErrorCode {
19071914
public static final int STEP_SIZE_MUST_NOT_BE_ZERO = 90142;
19081915

19091916

1910-
// next are 90039, 90051, 90056, 90110, 90122, 90143
1917+
// next are 90051, 90056, 90110, 90122, 90143
19111918

19121919
private ErrorCode() {
19131920
// utility class

h2/src/main/org/h2/engine/DbSettings.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ public class DbSettings extends SettingsBase {
133133
*/
134134
public final boolean largeTransactions = get("LARGE_TRANSACTIONS", true);
135135

136+
/**
137+
* Database setting <code>LOB_TIMEOUT</code> (default: 300000,
138+
* which means 5 minutes).<br />
139+
* The number of milliseconds a temporary LOB reference is kept until it
140+
* times out. After the timeout, the LOB is no longer accessible using this
141+
* reference.
142+
*/
143+
public final int lobTimeout = get("LOB_TIMEOUT", 300000);
144+
136145
/**
137146
* Database setting <code>MAX_COMPACT_COUNT</code>
138147
* (default: Integer.MAX_VALUE).<br />

h2/src/main/org/h2/engine/Session.java

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.HashMap;
1010
import java.util.HashSet;
1111
import java.util.Iterator;
12+
import java.util.LinkedList;
1213
import java.util.Random;
1314

1415
import org.h2.api.ErrorCode;
@@ -108,6 +109,20 @@ public class Session extends SessionWithState {
108109
private final int queryCacheSize;
109110
private SmallLRUCache<String, Command> queryCache;
110111
private long modificationMetaID = -1;
112+
113+
/**
114+
* Temporary LOBs from result sets. Those are kept for some time. The
115+
* problem is that transactions are committed before the result is returned,
116+
* and in some cases the next transaction is already started before the
117+
* result is read (for example when using the server mode, when accessing
118+
* metadata methods). We can't simply free those values up when starting the
119+
* next transaction, because they would be removed too early.
120+
*/
121+
private LinkedList<TimeoutValue> temporaryResultLobs;
122+
123+
/**
124+
* The temporary LOBs that need to be removed on commit.
125+
*/
111126
private ArrayList<Value> temporaryLobs;
112127

113128
private Transaction transaction;
@@ -497,14 +512,7 @@ public void commit(boolean ddl) {
497512
// (create/drop table and so on)
498513
database.commit(this);
499514
}
500-
if (temporaryLobs != null) {
501-
for (Value v : temporaryLobs) {
502-
if (!v.isLinked()) {
503-
v.close();
504-
}
505-
}
506-
temporaryLobs.clear();
507-
}
515+
removeTemporaryLobs(true);
508516
if (undoLog.size() > 0) {
509517
// commit the rows when using MVCC
510518
if (database.isMultiVersion()) {
@@ -536,6 +544,31 @@ public void commit(boolean ddl) {
536544
endTransaction();
537545
}
538546

547+
private void removeTemporaryLobs(boolean onTimeout) {
548+
if (temporaryLobs != null) {
549+
for (Value v : temporaryLobs) {
550+
if (!v.isLinked()) {
551+
v.close();
552+
}
553+
}
554+
temporaryLobs.clear();
555+
}
556+
if (temporaryResultLobs != null && temporaryResultLobs.size() > 0) {
557+
long keepYoungerThan = System.currentTimeMillis() -
558+
database.getSettings().lobTimeout;
559+
while (temporaryResultLobs.size() > 0) {
560+
TimeoutValue tv = temporaryResultLobs.getFirst();
561+
if (onTimeout && tv.created >= keepYoungerThan) {
562+
break;
563+
}
564+
Value v = temporaryResultLobs.removeFirst().value;
565+
if (!v.isLinked()) {
566+
v.close();
567+
}
568+
}
569+
}
570+
}
571+
539572
private void checkCommitRollback() {
540573
if (commitOrRollbackDisabled && locks.size() > 0) {
541574
throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
@@ -545,8 +578,8 @@ private void checkCommitRollback() {
545578
private void endTransaction() {
546579
if (unlinkLobMap != null && unlinkLobMap.size() > 0) {
547580
if (database.getMvStore() == null) {
548-
// need to flush the transaction log, because we can't unlink lobs
549-
// if the commit record is not written
581+
// need to flush the transaction log, because we can't unlink
582+
// lobs if the commit record is not written
550583
database.flush();
551584
}
552585
for (Value v : unlinkLobMap.values()) {
@@ -673,6 +706,7 @@ public void close() {
673706
if (!closed) {
674707
try {
675708
database.checkPowerOff();
709+
removeTemporaryLobs(false);
676710
cleanTempTables(true);
677711
undoLog.clear();
678712
database.removeSession(this);
@@ -1447,10 +1481,17 @@ public void endStatement() {
14471481

14481482
@Override
14491483
public void addTemporaryLob(Value v) {
1450-
if (temporaryLobs == null) {
1451-
temporaryLobs = new ArrayList<Value>();
1484+
if (v.getTableId() == LobStorageFrontend.TABLE_RESULT) {
1485+
if (temporaryResultLobs == null) {
1486+
temporaryResultLobs = new LinkedList<TimeoutValue>();
1487+
}
1488+
temporaryResultLobs.add(new TimeoutValue(v));
1489+
} else {
1490+
if (temporaryLobs == null) {
1491+
temporaryLobs = new ArrayList<Value>();
1492+
}
1493+
temporaryLobs.add(v);
14521494
}
1453-
temporaryLobs.add(v);
14541495
}
14551496

14561497
/**
@@ -1470,4 +1511,25 @@ public static class Savepoint {
14701511
long transactionSavepoint;
14711512
}
14721513

1514+
/**
1515+
* An object with a timeout.
1516+
*/
1517+
public static class TimeoutValue {
1518+
1519+
/**
1520+
* The time when this object was created.
1521+
*/
1522+
final long created = System.currentTimeMillis();
1523+
1524+
/**
1525+
* The value.
1526+
*/
1527+
final Value value;
1528+
1529+
TimeoutValue(Value v) {
1530+
this.value = v;
1531+
}
1532+
1533+
}
1534+
14731535
}

h2/src/main/org/h2/result/LocalResult.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
4242
private boolean distinct;
4343
private boolean randomAccess;
4444
private boolean closed;
45+
private boolean containsLobs;
4546

4647
/**
4748
* Construct a local result object.
@@ -114,12 +115,15 @@ public static LocalResult read(Session session, ResultSet rs, int maxrows) {
114115
* (if there is any) is not copied.
115116
*
116117
* @param targetSession the session of the copy
117-
* @return the copy
118+
* @return the copy if possible, or null if copying is not possible
118119
*/
119120
public LocalResult createShallowCopy(Session targetSession) {
120121
if (external == null && (rows == null || rows.size() < rowCount)) {
121122
return null;
122123
}
124+
if (containsLobs) {
125+
return null;
126+
}
123127
ResultExternal e2 = null;
124128
if (external != null) {
125129
e2 = external.createShallowCopy();
@@ -260,6 +264,7 @@ private void cloneLobs(Value[] values) {
260264
Value v = values[i];
261265
Value v2 = v.copyToResult();
262266
if (v2 != v) {
267+
containsLobs = true;
263268
session.addTemporaryLob(v2);
264269
values[i] = v2;
265270
}

h2/src/main/org/h2/store/LobStorageMap.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,14 @@ public InputStream getInputStream(ValueLobDb lob, byte[] hmac, long byteCount)
268268
init();
269269
Object[] value = lobMap.get(lob.getLobId());
270270
if (value == null) {
271-
throw DbException.throwInternalError("Lob not found: " + lob.getLobId());
271+
if (lob.getTableId() == LobStorageFrontend.TABLE_RESULT ||
272+
lob.getTableId() == LobStorageFrontend.TABLE_ID_SESSION_VARIABLE) {
273+
throw DbException.get(
274+
ErrorCode.LOB_CLOSED_ON_TIMEOUT_1, "" +
275+
lob.getLobId() + "/" + lob.getTableId());
276+
}
277+
throw DbException.throwInternalError("Lob not found: " +
278+
lob.getLobId() + "/" + lob.getTableId());
272279
}
273280
byte[] streamStoreId = (byte[]) value[0];
274281
return streamStore.get(streamStoreId);
@@ -348,7 +355,7 @@ private void removeLob(int tableId, long lobId) {
348355
}
349356

350357
private static void trace(String op) {
351-
System.out.println(Thread.currentThread().getName() + " LOB " + op);
358+
System.out.println("[" + Thread.currentThread().getName() + "] LOB " + op);
352359
}
353360

354361
}

h2/src/main/org/h2/value/ValueLobDb.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ public Value convertTo(int t) {
204204
@Override
205205
public boolean isLinked() {
206206
return tableId != LobStorageFrontend.TABLE_ID_SESSION_VARIABLE &&
207+
tableId != LobStorageFrontend.TABLE_RESULT &&
207208
small == null;
208209
}
209210

h2/src/test/org/h2/test/db/TestLob.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public static void main(String... a) throws Exception {
5959

6060
@Override
6161
public void test() throws Exception {
62+
testRemovedAfterTimeout();
6263
testConcurrentRemoveRead();
6364
testCloseLobTwice();
6465
testCleaningUpLobsOnRollback();
@@ -112,6 +113,42 @@ public void test() throws Exception {
112113
FileUtils.deleteRecursive(TEMP_DIR, true);
113114
}
114115

116+
private void testRemovedAfterTimeout() throws Exception {
117+
deleteDb("lob");
118+
final String url = getURL("lob;lob_timeout=50", true);
119+
Connection conn = getConnection(url);
120+
Statement stat = conn.createStatement();
121+
stat.execute("create table test(id int primary key, data clob)");
122+
PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)");
123+
prep.setInt(1, 1);
124+
prep.setString(2, "aaa" + new String(new char[1024 * 16]).replace((char) 0, 'x'));
125+
prep.execute();
126+
prep.setInt(1, 2);
127+
prep.setString(2, "bbb" + new String(new char[1024 * 16]).replace((char) 0, 'x'));
128+
prep.execute();
129+
ResultSet rs = stat.executeQuery("select * from test order by id");
130+
rs.next();
131+
Clob c1 = rs.getClob(2);
132+
assertEquals("aaa", c1.getSubString(1, 3));
133+
rs.next();
134+
assertEquals("aaa", c1.getSubString(1, 3));
135+
rs.close();
136+
assertEquals("aaa", c1.getSubString(1, 3));
137+
stat.execute("delete from test");
138+
c1.getSubString(1, 3);
139+
// wait until it times out
140+
Thread.sleep(100);
141+
// start a new transaction, to be sure
142+
stat.execute("delete from test");
143+
try {
144+
c1.getSubString(1, 3);
145+
fail();
146+
} catch (SQLException e) {
147+
// expected
148+
}
149+
conn.close();
150+
}
151+
115152
private void testConcurrentRemoveRead() throws Exception {
116153
deleteDb("lob");
117154
final String url = getURL("lob", true);
@@ -1255,7 +1292,6 @@ private void testUpdateLob() throws SQLException {
12551292
"CREATE TABLE IF NOT EXISTS p( id int primary key, rawbyte BLOB ); ");
12561293
prep.execute();
12571294
prep.close();
1258-
12591295
prep = conn.prepareStatement("INSERT INTO p(id) VALUES(?);");
12601296
for (int i = 0; i < 10; i++) {
12611297
prep.setInt(1, i);

0 commit comments

Comments
 (0)