提交 ad08cb5e authored 作者: Noel Grandin's avatar Noel Grandin 提交者: avp1983

Issue #324: Deadlock when sending BLOBs over TCP

上级 add3db91
...@@ -21,6 +21,8 @@ Change Log ...@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <ul>
<li>Issue #324: Deadlock when sending BLOBs over TCP
</li>
<li>Fix for creating and accessing views in MULTITHREADED mode, test-case courtesy of Daniel Rosenbaum <li>Fix for creating and accessing views in MULTITHREADED mode, test-case courtesy of Daniel Rosenbaum
</li> </li>
<li>Issue #266: Spatial index not updating, fixed by merging PR #267 <li>Issue #266: Spatial index not updating, fixed by merging PR #267
......
...@@ -1602,10 +1602,11 @@ public class Function extends Expression implements FunctionCall { ...@@ -1602,10 +1602,11 @@ public class Function extends Expression implements FunctionCall {
String fileName = v0.getString(); String fileName = v0.getString();
boolean blob = args.length == 1; boolean blob = args.length == 1;
try { try {
long fileLength = FileUtils.size(fileName);
InputStream in = new AutoCloseInputStream( InputStream in = new AutoCloseInputStream(
FileUtils.newInputStream(fileName)); FileUtils.newInputStream(fileName));
if (blob) { if (blob) {
result = database.getLobStorage().createBlob(in, -1); result = database.getLobStorage().createBlob(in, fileLength);
} else { } else {
Reader reader; Reader reader;
if (v1 == ValueNull.INSTANCE) { if (v1 == ValueNull.INSTANCE) {
...@@ -1613,7 +1614,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1613,7 +1614,7 @@ public class Function extends Expression implements FunctionCall {
} else { } else {
reader = new InputStreamReader(in, v1.getString()); reader = new InputStreamReader(in, v1.getString());
} }
result = database.getLobStorage().createClob(reader, -1); result = database.getLobStorage().createClob(reader, fileLength);
} }
session.addTemporaryLob(result); session.addTemporaryLob(result);
} catch (IOException e) { } catch (IOException e) {
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
23506=Нарушение ссылочной целостности: {0} 23506=Нарушение ссылочной целостности: {0}
23507=Для поля {0} не установлено значение по умолчанию 23507=Для поля {0} не установлено значение по умолчанию
23513=Нарушение ограничения: {0} 23513=Нарушение ограничения: {0}
23514=#Check constraint invalid: {0} 23514=Неправильное ограничение CHECK: {0}
28000=Неверное имя пользователя или пароль 28000=Неверное имя пользователя или пароль
40001=Обнаружена взаимная блокировка потоков. Текущая транзакция была откачена. Детали: {0} 40001=Обнаружена взаимная блокировка потоков. Текущая транзакция была откачена. Детали: {0}
42000=Синтаксическая ошибка в выражении SQL {0} 42000=Синтаксическая ошибка в выражении SQL {0}
...@@ -33,12 +33,12 @@ ...@@ -33,12 +33,12 @@
90002=Метод разрешен только для запросов. Используйте execute или executeUpdate вместо executeQuery 90002=Метод разрешен только для запросов. Используйте execute или executeUpdate вместо executeQuery
90003=Шестнадцатиричная строка содержит нечетное количество символов: {0} 90003=Шестнадцатиричная строка содержит нечетное количество символов: {0}
90004=Шестнадцатиричная строка содержит нешестнадцатиричные символы: {0} 90004=Шестнадцатиричная строка содержит нешестнадцатиричные символы: {0}
90006=#Sequence {0} has run out of numbers 90006=Последовательность {0} вышла за границы (MINVALUE, MAXVALUE)
90007=Объект уже закрыт 90007=Объект уже закрыт
90008=Недопустимое значение {0} для параметра {1} 90008=Недопустимое значение {0} для параметра {1}
90009=#Unable to create or alter sequence {0} because of invalid attributes (start value {1}, min value {2}, max value {3}, increment {4}) 90009=Невозможно создать или изменить последовательность {0} из-за неправильных атрибутов (START/RESTART {1}, MINVALUE {2}, MAXVALUE {3}, INCREMENT {4})
90010=#Invalid TO_CHAR format {0} 90010=Неправильный формат TO_CHAR {0}
90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. 90011=Путь к файлу, который неявен относительно текущего рабочего каталога не допускается в URL базы данных {0}. Используйте абсолютный путь, ~/name, ./name, или настройку baseDir.
90012=Параметр {0} не установлен 90012=Параметр {0} не установлен
90013=База данных {0} не найдена 90013=База данных {0} не найдена
90014=Ошибка при разборе {0} 90014=Ошибка при разборе {0}
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
90018=Незакрытое приложением соединение уничтожено сборщиком мусора 90018=Незакрытое приложением соединение уничтожено сборщиком мусора
90019=Невозможно удалить текущего пользователя 90019=Невозможно удалить текущего пользователя
90020=База данных уже используется: {0}. Возможные решения: закрыть все другие соединения; использовать режим сервера 90020=База данных уже используется: {0}. Возможные решения: закрыть все другие соединения; использовать режим сервера
90021=#This combination of database settings is not supported: {0} 90021=Такое сочетание настроек базы данных не поддерживается: {0}
90022=Функция {0} не найдена 90022=Функция {0} не найдена
90023=Поле {0} не должно поддерживать значение NULL 90023=Поле {0} не должно поддерживать значение NULL
90024=Ошибка при переименовании файла {0} в {1} 90024=Ошибка при переименовании файла {0} в {1}
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
90036=Последовательность {0} не найдена 90036=Последовательность {0} не найдена
90037=Представление {0} не найдено 90037=Представление {0} не найдено
90038=Представление {0} уже существует 90038=Представление {0} уже существует
90039=#This CLOB or BLOB reference timed out: {0} 90039=Этот CLOB или BLOB объект закрыт по таймауту: {0}
90040=Для выполнения данной операции необходимы права администратора 90040=Для выполнения данной операции необходимы права администратора
90041=Триггер {0} уже существует 90041=Триггер {0} уже существует
90042=Триггер {0} не найден 90042=Триггер {0} не найден
...@@ -78,12 +78,12 @@ ...@@ -78,12 +78,12 @@
90048=Неподдерживаемая версия файлов базы данных или некорректный заголовок в файле {0} 90048=Неподдерживаемая версия файлов базы данных или некорректный заголовок в файле {0}
90049=Ошибка шифрования в файле {0} 90049=Ошибка шифрования в файле {0}
90050=Некорректный формат пароля, должен быть: пароль файла <пробел> пароль пользователя 90050=Некорректный формат пароля, должен быть: пароль файла <пробел> пароль пользователя
90051=#Scale(${0}) must not be bigger than precision({1}) 90051=Количество цифр после разделителя (scale) (${0}) не должно быть больше общего количества цифр (precision) ({1})
90052=Подзапрос выбирает более одного столбца 90052=Подзапрос выбирает более одного столбца
90053=Подзапрос выбирает более одной строки 90053=Подзапрос выбирает более одной строки
90054=Некорректное использование агрегирующей функции {0} 90054=Некорректное использование агрегирующей функции {0}
90055=Метод шифрования {0} не поддерживается 90055=Метод шифрования {0} не поддерживается
90056=#Function {0}: Invalid date format: {1} 90056=Функция {0}: Неверный формат даты: {1}
90057=Ограничение {0} не найдено 90057=Ограничение {0} не найдено
90058=Commit или rollback внутри триггера не допускается 90058=Commit или rollback внутри триггера не допускается
90059=Неоднозначное имя столбца {0} 90059=Неоднозначное имя столбца {0}
...@@ -165,8 +165,8 @@ ...@@ -165,8 +165,8 @@
90138=Недопустимое имя базы данных: {0} 90138=Недопустимое имя базы данных: {0}
90139=public static Java метод не найден: {0} 90139=public static Java метод не найден: {0}
90140=Набор записей не является обновляемым. Возможно необходимо использовать conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). 90140=Набор записей не является обновляемым. Возможно необходимо использовать conn.createStatement(.., ResultSet.CONCUR_UPDATABLE).
90141=#Serializer cannot be changed because there is a data table: {0} 90141=Serializer не может быть изменен, потому что есть таблица данных: {0}
90142=#Step size must not be zero 90142=Размер шага не должен быть равен нулю
HY000=Внутренняя ошибка: {0} HY000=Внутренняя ошибка: {0}
HY004=Неизвестный тип данных: {0} HY004=Неизвестный тип данных: {0}
HYC00=Данная функция не поддерживается: {0} HYC00=Данная функция не поддерживается: {0}
......
...@@ -12,11 +12,10 @@ import java.nio.ByteBuffer; ...@@ -12,11 +12,10 @@ import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction; import java.nio.charset.CodingErrorAction;
import org.h2.engine.Constants; import org.h2.engine.Constants;
/** /**
* An input stream that reads the data from a reader. * An input stream that reads the data from a reader and limits the number of bytes that can be read.
*/ */
public class CountingReaderInputStream extends InputStream { public class CountingReaderInputStream extends InputStream {
......
...@@ -5,15 +5,12 @@ ...@@ -5,15 +5,12 @@
*/ */
package org.h2.store; package org.h2.store;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
...@@ -145,26 +142,21 @@ public class LobStorageMap implements LobStorageInterface { ...@@ -145,26 +142,21 @@ public class LobStorageMap implements LobStorageInterface {
public Value createBlob(InputStream in, long maxLength) { public Value createBlob(InputStream in, long maxLength) {
init(); init();
int type = Value.BLOB; int type = Value.BLOB;
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
}
int max = (int) Math.min(maxLength, database.getMaxLengthInplaceLob());
try { try {
if (max != 0 && max < Integer.MAX_VALUE) { if (maxLength != -1
BufferedInputStream b = new BufferedInputStream(in, max); && maxLength <= database.getMaxLengthInplaceLob()) {
b.mark(max); byte[] small = new byte[(int) maxLength];
byte[] small = new byte[max]; int len = IOUtils.readFully(in, small, (int) maxLength);
int len = IOUtils.readFully(b, small, max); if (len > maxLength) {
if (len < max) { throw new IllegalStateException(
if (len < small.length) { "len > blobLength, " + len + " > " + maxLength);
small = Arrays.copyOf(small, len); }
} if (len < small.length) {
return ValueLobDb.createSmallLob(type, small); small = Arrays.copyOf(small, len);
} }
b.reset(); return ValueLobDb.createSmallLob(type, small);
in = b;
} }
if (maxLength != Long.MAX_VALUE) { if (maxLength != -1) {
in = new LimitInputStream(in, maxLength); in = new LimitInputStream(in, maxLength);
} }
return createLob(in, type); return createLob(in, type);
...@@ -179,32 +171,34 @@ public class LobStorageMap implements LobStorageInterface { ...@@ -179,32 +171,34 @@ public class LobStorageMap implements LobStorageInterface {
public Value createClob(Reader reader, long maxLength) { public Value createClob(Reader reader, long maxLength) {
init(); init();
int type = Value.CLOB; int type = Value.CLOB;
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
}
int max = (int) Math.min(maxLength, database.getMaxLengthInplaceLob());
try { try {
if (max != 0 && max < Integer.MAX_VALUE) { // we multiple by 3 here to get the worst-case size in bytes
BufferedReader b = new BufferedReader(reader, max); if (maxLength != -1
b.mark(max); && maxLength * 3 <= database.getMaxLengthInplaceLob()) {
char[] small = new char[max]; char[] small = new char[(int) maxLength];
int len = IOUtils.readFully(b, small, max); int len = IOUtils.readFully(reader, small, (int) maxLength);
if (len < max) { if (len > maxLength) {
if (len < small.length) { throw new IllegalStateException(
small = Arrays.copyOf(small, len); "len > blobLength, " + len + " > " + maxLength);
} }
byte[] utf8 = new String(small, 0, len).getBytes(Constants.UTF8); byte[] utf8 = new String(small, 0, len)
return ValueLobDb.createSmallLob(type, utf8); .getBytes(Constants.UTF8);
if (utf8.length > database.getMaxLengthInplaceLob()) {
throw new IllegalStateException(
"len > maxinplace, " + utf8.length + " > "
+ database.getMaxLengthInplaceLob());
} }
b.reset(); return ValueLobDb.createSmallLob(type, utf8);
reader = b; }
if (maxLength < 0) {
maxLength = Long.MAX_VALUE;
} }
CountingReaderInputStream in = CountingReaderInputStream in = new CountingReaderInputStream(reader,
new CountingReaderInputStream(reader, maxLength); maxLength);
ValueLobDb lob = createLob(in, type); ValueLobDb lob = createLob(in, type);
// the length is not correct // the length is not correct
lob = ValueLobDb.create(type, database, lob = ValueLobDb.create(type, database, lob.getTableId(),
lob.getTableId(), lob.getLobId(), null, in.getLength()); lob.getLobId(), null, in.getLength());
return lob; return lob;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e); throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
......
...@@ -1046,10 +1046,10 @@ public class DataType { ...@@ -1046,10 +1046,10 @@ public class DataType {
createClob(r, -1); createClob(r, -1);
} else if (x instanceof java.sql.Clob) { } else if (x instanceof java.sql.Clob) {
try { try {
Reader r = new BufferedReader( java.sql.Clob clob = (java.sql.Clob) x;
((java.sql.Clob) x).getCharacterStream()); Reader r = new BufferedReader(clob.getCharacterStream());
return session.getDataHandler().getLobStorage(). return session.getDataHandler().getLobStorage().
createClob(r, -1); createClob(r, clob.length());
} catch (SQLException e) { } catch (SQLException e) {
throw DbException.convert(e); throw DbException.convert(e);
} }
...@@ -1058,8 +1058,9 @@ public class DataType { ...@@ -1058,8 +1058,9 @@ public class DataType {
createBlob((java.io.InputStream) x, -1); createBlob((java.io.InputStream) x, -1);
} else if (x instanceof java.sql.Blob) { } else if (x instanceof java.sql.Blob) {
try { try {
java.sql.Blob blob = (java.sql.Blob) x;
return session.getDataHandler().getLobStorage(). return session.getDataHandler().getLobStorage().
createBlob(((java.sql.Blob) x).getBinaryStream(), -1); createBlob(blob.getBinaryStream(), blob.length());
} catch (SQLException e) { } catch (SQLException e) {
throw DbException.convert(e); throw DbException.convert(e);
} }
......
...@@ -23,7 +23,6 @@ import java.sql.SQLException; ...@@ -23,7 +23,6 @@ import java.sql.SQLException;
import java.sql.Savepoint; import java.sql.Savepoint;
import java.sql.Statement; import java.sql.Statement;
import java.util.Random; import java.util.Random;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.jdbc.JdbcConnection; import org.h2.jdbc.JdbcConnection;
...@@ -84,6 +83,7 @@ public class TestLob extends TestBase { ...@@ -84,6 +83,7 @@ public class TestLob extends TestBase {
testDelete(); testDelete();
testLobServerMemory(); testLobServerMemory();
testUpdatingLobRow(); testUpdatingLobRow();
testBufferedInputStreamBug();
if (config.memory) { if (config.memory) {
return; return;
} }
...@@ -1505,6 +1505,17 @@ public class TestLob extends TestBase { ...@@ -1505,6 +1505,17 @@ public class TestLob extends TestBase {
conn.close(); conn.close();
} }
/** test a bug where the usage of BufferedInputStream in LobStorageMap was causing a deadlock */
private void testBufferedInputStreamBug() throws SQLException {
deleteDb("lob");
JdbcConnection conn = (JdbcConnection) getConnection("lob");
conn.createStatement().execute("CREATE TABLE TEST(test BLOB)");
PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST(test) VALUES(?)");
ps.setBlob(1, new ByteArrayInputStream(new byte[257]));
ps.executeUpdate();
conn.close();
}
private static Reader getRandomReader(int len, int seed) { private static Reader getRandomReader(int len, int seed) {
return new CharArrayReader(getRandomChars(len, seed)); return new CharArrayReader(getRandomChars(len, seed));
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论