Резюме: когда я использую Thrift для сериализации карты в C ++ на диск, а затем десериализации ее с помощью Python, я не возвращаю тот же объект.
Минимальный пример воспроизведения проблемы - Github repo https://github.com/brunorijsman/reproduce-thrift-crash
Клонируйте этот репозиторий в Ubuntu (протестирован 16.04) и следуйте инструкциям в верхней части файла размножайтесь .sh
У меня есть следующий файл модели Thrift, который (как вы можете видеть) содержит карту, проиндексированную структурой:
struct Coordinate {
1: required i32 x;
2: required i32 y;
}
struct Terrain {
1: required map<Coordinate, i32> altitude_samples;
}
Я использую следующий код C ++ для создания объекта с 3-мя координатами на карте (полный код для всех фрагментов приведен ниже в репозитории):
Terrain terrain;
add_sample_to_terrain(terrain, 10, 10, 100);
add_sample_to_terrain(terrain, 20, 20, 200);
add_sample_to_terrain(terrain, 30, 30, 300);
где:
void add_sample_to_terrain(Terrain& terrain, int32_t x, int32_t y, int32_t altitude)
{
Coordinate coordinate;
coordinate.x = x;
coordinate.y = y;
std::pair<Coordinate, int32_t> sample(coordinate, altitude);
terrain.altitude_samples.insert(sample);
}
Я использую следующий код C ++ для сериализации объекта на диск:
shared_ptr<TFileTransport> transport(new TFileTransport("terrain.dat"));
shared_ptr<TBinaryProtocol> protocol(new TBinaryProtocol(transport));
terrain.write(protocol.get());
Важное замечание: чтобы это работало правильно, мне пришлось реализовать функцию Coordinate :: operator <. Thrift генерирует объявление для Coordinate :: operator <, но не генерирует реализацию Coordinate :: operator <. Причина этого заключается в том, что Thrift не понимает семантику структуры и, следовательно, не может угадать правильную реализацию оператора сравнения. Это обсуждается на <a href="http://mail-archives.apache.org/mod_mbox/thrift-user/201007.mbox/%3C4C4E08BD.8030407@facebook.com%3E" rel="nofollow noreferrer">http://mail-archives.apache.org/mod_mbox/thrift-user/201007.mbox/%3C4C4E08BD.8030407@facebook.com%3E
// Thrift generates the declaration but not the implementation of operator< because it has no way
// of knowning what the criteria for the comparison are. So, provide the implementation here.
bool Coordinate::operator<(const Coordinate& other) const
{
if (x < other.x) {
return true;
} else if (x > other.x) {
return false;
} else if (y < other.y) {
return true;
} else {
return false;
}
}
Затем, наконец, я использую следующий код Python для десериализации того же объекта с диска:
file = open("terrain.dat", "rb")
transport = thrift.transport.TTransport.TFileObjectTransport(file)
protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(transport)
terrain = Terrain()
terrain.read(protocol)
print(terrain)
Эта программа Python выводит:
Terrain(altitude_samples=None)
Другими словами, десериализованный Terrain не содержит terrain_samples вместо ожидаемого словаря с 3 координатами.
Я на 100% уверен, что файл terrain.dat содержит действительные данные: я также десериализовал те же данные, используя C ++, и в этом случае я do получаю ожидаемые результаты (подробности см. В репозитории). )
Я подозреваю, что это как-то связано с оператором сравнения.
У меня такое ощущение, что я должен был сделать что-то подобное в Python в отношении оператора сравнения, как я делал в C ++. Но я не знаю, что это будет за упущенное.
Дополнительная информация добавлена 19 сентября 2018 г .:
Вот hexdump кодировки, созданной программой кодирования C ++:
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 01 00 00 00 0D 02 00 00 00 00 01 01 00 00 00 0C ................
00000010: 01 00 00 00 08 04 00 00 00 00 00 00 03 01 00 00 ................
00000020: 00 08 02 00 00 00 00 01 04 00 00 00 00 00 00 0A ................
00000030: 01 00 00 00 08 02 00 00 00 00 02 04 00 00 00 00 ................
00000040: 00 00 0A 01 00 00 00 00 04 00 00 00 00 00 00 64 ...............d
00000050: 01 00 00 00 08 02 00 00 00 00 01 04 00 00 00 00 ................
00000060: 00 00 14 01 00 00 00 08 02 00 00 00 00 02 04 00 ................
00000070: 00 00 00 00 00 14 01 00 00 00 00 04 00 00 00 00 ................
00000080: 00 00 C8 01 00 00 00 08 02 00 00 00 00 01 04 00 ..H.............
00000090: 00 00 00 00 00 1E 01 00 00 00 08 02 00 00 00 00 ................
000000a0: 02 04 00 00 00 00 00 00 1E 01 00 00 00 00 04 00 ................
000000b0: 00 00 00 00 01 2C 01 00 00 00 00 .....,.....
Первые 4 байта: 01 00 00 00
Использование отладчика для пошагового выполнения функции декодирования Python показывает, что:
Это декодируется как структура (что ожидается)
Первый байт 01 интерпретируется как тип поля. 01 означает тип поля VOID.
Следующие два байта интерпретируются как идентификатор поля. 00 00 означает идентификатор поля 0.
Для типа поля VOID больше ничего не читается, и мы переходим к следующему полю.
Следующий байт интерпретируется как тип поля. 00 означает STOP.
Мы читаем данные для структуры.
Окончательный результат - пустая структура.
Все вышеперечисленное согласуется с информацией в https://github.com/apache/thrift/blob/master/doc/specs/thrift-binary-protocol.md, которая описывает формат двоичного кодирования Thrift
На данный момент я пришел к выводу, что кодировщик C ++, по-видимому, производит «неправильную» двоичную кодировку (в кавычках я указывал неверное, потому что, безусловно, что-то столь вопиющее, как это было бы обнаружено многими другими людьми, поэтому я уверен, что я я все еще что-то упускаю).
Дополнительная информация добавлена 19 сентября 2018 года:
Похоже, что реализация TFileTransport на C ++ имеет концепцию "событий" при записи на диск.
Выходные данные, которые записываются на диск, делятся на последовательность «событий», где каждому «событию» предшествует 4-байтовое поле длины события, за которым следует содержимое события.
Глядя на hexdump выше, первая пара событий:
0100 0000 0d
: длина события 1, значение события 0d
02 0000 0000 01
: длина события 2, значение события 00 01
1103 * Etc. *
Реализация TFileTransport на Python не понимает эту концепцию событий при разборе файла.
Похоже, проблема в одном из следующих:
1) Либо код C ++ не должен вставлять эти длины событий в закодированный файл,
2) Или код Python должен понимать эти длины событий при декодировании файла.
Обратите внимание, что все эти длины событий делают кодированный файл C ++ намного больше, чем кодированный файл Python.