mirror of
https://github.com/isar/libmdbx.git
synced 2025-01-04 17:44:13 +08:00
mdbx: Merge branch 'master' into 'nexenta' branch.
Change-Id: Iade70bae46bc4ea4baac36b3ed86434519959274
This commit is contained in:
commit
c0c4742dba
15
.travis.yml
Normal file
15
.travis.yml
Normal file
@ -0,0 +1,15 @@
|
||||
language: c
|
||||
sudo: false
|
||||
dist: trusty
|
||||
cache: bundler
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make all lmdb check; fi
|
19
CHANGES
19
CHANGES
@ -1,16 +1,29 @@
|
||||
MDBX
|
||||
Add MDB_PREV_MULTIPLE
|
||||
Fix MDB_CP_COMPACT (ITS#8209)
|
||||
Add error MDB_PROBLEM, replace some MDB_CORRUPTED
|
||||
Backport fixes for ITS#8406
|
||||
Workarounds for glibc bugs: #21031 and 21032.
|
||||
|
||||
LMDB 0.9.19 Release Engineering
|
||||
LMDB 0.9.20 Release Engineering
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
already done for mdbx - Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
|
@ -1,6 +1,6 @@
|
||||
Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
2
Doxyfile
2
Doxyfile
@ -253,7 +253,7 @@ IDL_PROPERTY_SUPPORT = YES
|
||||
# member in the group (if any) for the other members of the group. By default
|
||||
# all members of a group must be documented explicitly.
|
||||
|
||||
DISTRIBUTE_GROUP_DOC = NO
|
||||
DISTRIBUTE_GROUP_DOC = YES
|
||||
|
||||
# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
|
||||
# the same type (for instance a group of public functions) to be put as a
|
||||
|
10
Makefile
10
Makefile
@ -24,8 +24,8 @@ suffix ?=
|
||||
|
||||
CC ?= gcc
|
||||
XCFLAGS ?= -DNDEBUG=1 -DMDB_DEBUG=0
|
||||
CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra
|
||||
CFLAGS += -pthread $(XCFLAGS)
|
||||
CFLAGS ?= -O2 -g3 -Wall -Werror -Wextra -ffunction-sections
|
||||
CFLAGS += -std=gnu99 -pthread $(XCFLAGS)
|
||||
|
||||
# LY: for ability to built with modern glibc,
|
||||
# but then run with the old
|
||||
@ -207,13 +207,15 @@ bench: bench-lmdb.txt bench-mdbx.txt
|
||||
endif
|
||||
|
||||
ci-rule = ( CC=$$(which $1); if [ -n "$$CC" ]; then \
|
||||
echo -n "probe by $2 ($$CC): " && \
|
||||
echo -n "probe by $2 ($$(readlink -f $$(which $$CC))): " && \
|
||||
$(MAKE) clean >$1.log 2>$1.err && \
|
||||
$(MAKE) CC=$$(readlink -f $$CC) XCFLAGS="-UNDEBUG -DMDB_DEBUG=2" all check 1>$1.log 2>$1.err && echo "OK" \
|
||||
|| ( echo "FAILED"; cat $1.err >&2; exit 1 ); \
|
||||
else echo "no $2 ($1) for probe"; fi; )
|
||||
ci:
|
||||
@if [ "$(CC)" != "gcc" ]; then \
|
||||
@if [ "$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which gcc || echo /bin/false))" -a \
|
||||
"$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which clang || echo /bin/false))" -a \
|
||||
"$$(readlink -f $$(which $(CC)))" != "$$(readlink -f $$(which icc || echo /bin/false))" ]; then \
|
||||
$(call ci-rule,$(CC),default C compiler); \
|
||||
fi
|
||||
@$(call ci-rule,gcc,GCC)
|
||||
|
375
README.md
Normal file
375
README.md
Normal file
@ -0,0 +1,375 @@
|
||||
libmdbx
|
||||
======================================
|
||||
Extended LMDB, aka "Расширенная LMDB".
|
||||
|
||||
*The Future will Positive. Всё будет хорошо.*
|
||||
[![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=master)](https://travis-ci.org/ReOpen/libmdbx)
|
||||
|
||||
English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/master).
|
||||
|
||||
|
||||
## Кратко
|
||||
|
||||
_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
|
||||
набором возможностей, которые при правильном применении позволяют
|
||||
создавать уникальные решения с чемпионской производительностью, идеально
|
||||
сочетаясь с технологией [MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory).
|
||||
|
||||
_libmdbx_ обновляет совместно используемый набор данных, никак не мешая
|
||||
при этом параллельным операциям чтения, не применяя атомарных операций к
|
||||
самим данным, и обеспечивая согласованность при аварийной остановке в
|
||||
любой момент. Поэтому _libmdbx_ позволяя строить системы с линейным
|
||||
масштабированием производительности чтения/поиска по ядрам CPU и
|
||||
амортизационной стоимостью любых операций Olog(N).
|
||||
|
||||
### История
|
||||
|
||||
_libmdbx_ является потомком "Lightning Memory-Mapped Database",
|
||||
известной под аббревиатурой
|
||||
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
|
||||
Изначально доработка производилась в составе проекта
|
||||
[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год
|
||||
работы внесенные изменения приобрели самостоятельную ценность. Осенью
|
||||
2015 доработанный движок был выделен в отдельный проект, который был
|
||||
[представлен на конференции Highload++
|
||||
2015](http://www.highload.ru/2015/abstracts/1831.html).
|
||||
|
||||
|
||||
Характеристики и ключевые особенности
|
||||
=====================================
|
||||
|
||||
_libmdbx_ наследует все ключевые возможности и особенности от
|
||||
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
|
||||
с устранением описанных далее проблем и архитектурных недочетов.
|
||||
|
||||
### Общее для оригинальной _LMDB_ и _libmdbx_
|
||||
|
||||
1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
|
||||
отсортированы, поддерживается выборка диапазонов (range lookups).
|
||||
|
||||
2. Данные отображается в память каждого работающего с БД процесса.
|
||||
Ключам и данным обеспечивается прямой доступ без необходимости их
|
||||
копирования, так как они защищены транзакцией чтения и не изменяются.
|
||||
|
||||
3. Транзакции согласно
|
||||
[ACID](https://ru.wikipedia.org/wiki/ACID), посредством
|
||||
[MVCC](https://ru.wikipedia.org/wiki/MVCC) и
|
||||
[COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8).
|
||||
Изменения строго последовательны и не блокируются чтением,
|
||||
конфликты между транзакциями не возможны.
|
||||
|
||||
4. Чтение и поиск [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F),
|
||||
без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F).
|
||||
Читатели не блокируются операциями записи и не конкурируют
|
||||
между собой, чтение масштабируется линейно по ядрам CPU.
|
||||
|
||||
5. Эффективное хранение дубликатов (ключей с несколькими
|
||||
значениями), без дублирования ключей, с сортировкой значений, в
|
||||
том числе целочисленных (для вторичных индексов).
|
||||
|
||||
6. Эффективная поддержка ключей фиксированной длины, в том числе целочисленных.
|
||||
|
||||
7. Амортизационная стоимость любой операции Olog(N),
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N).
|
||||
|
||||
8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
|
||||
транзакций, после сбоев не требуется восстановление. Не требуется компактификация
|
||||
или какое-либо периодическое обслуживание. Поддерживается резервное копирование
|
||||
"по горячему", на работающей БД без приостановки изменения данных.
|
||||
|
||||
9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
|
||||
необходимое штатно выполняет ядро ОС.
|
||||
|
||||
|
||||
### Недостатки и Компромиссы
|
||||
|
||||
1. Единовременно может выполняться не более одной транзакция изменения данных
|
||||
(один писатель). Зато все изменения всегда последовательны, не может быть
|
||||
конфликтов или ошибок при откате транзакций.
|
||||
|
||||
2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
|
||||
обуславливает относительно большой
|
||||
[WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация
|
||||
изменений на диске может быть дорогой и является главным ограничителем для
|
||||
производительности по записи. В качестве компромисса предлагается несколько
|
||||
режимов ленивой и/или периодической фиксации. В том числе режим `MAPASYNC`,
|
||||
при котором изменения происходят только в памяти и асинхронно фиксируются на
|
||||
диске ядром ОС.
|
||||
|
||||
3. [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8)
|
||||
для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
|
||||
уровне страниц в [B+ дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
|
||||
Поэтому изменение данных амортизационно требует копирования Olog(N) страниц,
|
||||
что расходует [пропускную способность оперативной
|
||||
памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным
|
||||
ограничителем производительности в режиме `MAPASYNC`.
|
||||
|
||||
4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
|
||||
которая приводит к деградации производительности и переполнению БД.
|
||||
В _libmdbx_ предложены средства для предотвращения, выхода из проблемной
|
||||
ситуации и устранения её последствий. Подробности ниже.
|
||||
|
||||
5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
|
||||
В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
|
||||
так и согласованность данных. При этом также, в качестве альтернативы,
|
||||
предложен режим `UTTERLY_NOSYNC`. Подробности ниже.
|
||||
|
||||
|
||||
#### Проблема долгих чтений
|
||||
|
||||
Понимание проблемы требует некоторых пояснений, которые
|
||||
изложены ниже, но могут быть сложны для быстрого восприятия.
|
||||
Поэтому, тезисно:
|
||||
|
||||
* Изменение данных на фоне долгой операции чтения может
|
||||
приводить к исчерпанию места в БД.
|
||||
|
||||
* После чего любая попытка обновить данные будет приводить к
|
||||
ошибке `MAP_FULL` до завершения долгой операции чтения.
|
||||
|
||||
* Характерными примерами долгих чтений являются горячее
|
||||
резервное копирования и отладка клиентского приложения при
|
||||
активной транзакции чтения.
|
||||
|
||||
* В оригинальной _LMDB_ после этого будет наблюдаться
|
||||
устойчивая деградация производительности всех механизмов
|
||||
обратной записи на диск (в I/O контроллере, в гипервизоре,
|
||||
в ядре ОС).
|
||||
|
||||
* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
|
||||
операций, а также режим `LIFO RECLAIM` устраняющий последующую
|
||||
деградацию производительности.
|
||||
|
||||
Операции чтения выполняются в контексте снимка данных (версии
|
||||
БД), который был актуальным на момент старта транзакции чтения.
|
||||
Такой читаемый снимок поддерживается неизменным до завершения
|
||||
операции. В свою очередь, это не позволяет повторно
|
||||
использовать страницы БД в последующих версиях (снимках БД).
|
||||
|
||||
Другими словами, если обновление данных выполняется на фоне
|
||||
долгой операции чтения, то вместо повторного использования
|
||||
"старых" ненужных страниц будут выделяться новые, так как
|
||||
"старые" страницы составляют снимок БД, который еще
|
||||
используется долгой операцией чтения.
|
||||
|
||||
В результате, при интенсивном изменении данных и достаточно
|
||||
длительной операции чтения, в БД могут быть исчерпаны свободные
|
||||
страницы, что не позволит создавать новые снимки/версии БД.
|
||||
Такая ситуация будет сохраняться до завершения операции чтения,
|
||||
которая использует старый снимок данных и препятствует
|
||||
повторному использованию страниц БД.
|
||||
|
||||
Однако, на этом проблемы не заканчиваются. После описанной
|
||||
ситуации, все дополнительные страницы, которые были выделены
|
||||
пока переработка старых была невозможна, будут участвовать в
|
||||
цикле выделения/освобождения до конца жизни экземпляра БД. В
|
||||
оригинальной _LMDB_ этот цикл использования страниц работает по
|
||||
принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому
|
||||
увеличение количества циркулирующий страниц, с точки зрения
|
||||
механизмов кэширования и/или обратной записи, выглядит как
|
||||
увеличение рабочего набор данных. Проще говоря, однократное
|
||||
попадание в ситуацию "уснувшего читателя" приводит к
|
||||
устойчивому эффекту вымывания I/O кэша при всех последующих
|
||||
изменениях данных.
|
||||
|
||||
Для устранения описанных проблемы в _libmdbx_ сделаны
|
||||
существенные доработки, подробности ниже. Иллюстрации к
|
||||
проблеме "долгих чтений" можно найти в [слайдах
|
||||
презентации](http://www.slideshare.net/leoyuriev/lmdb).
|
||||
Там же приведен пример количественной оценки прироста
|
||||
производительности за счет эффективной работы
|
||||
[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO
|
||||
RECLAIM` в _libmdbx_.
|
||||
|
||||
|
||||
#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`
|
||||
|
||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных
|
||||
страниц выполняется ядром ОС, что имеет ряд преимуществ. Так
|
||||
например, при крахе приложения, ядро ОС сохранит все изменения.
|
||||
|
||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на
|
||||
диске будет сохранена только часть измененных страниц БД. При
|
||||
этом с большой вероятностью может оказаться так, что будут
|
||||
сохранены мета-страницы со ссылками на страницы с новыми
|
||||
версиями данных, но не сами новые данные. В этом случае БД
|
||||
будет безвозвратна разрушена, даже если до аварии производилась
|
||||
полная синхронизация данных (посредством `mdb_env_sync()`).
|
||||
|
||||
В _libmdbx_ эта проблема устранена, подробности ниже.
|
||||
|
||||
|
||||
Доработки _libmdbx_
|
||||
===================
|
||||
|
||||
1. Режим `LIFO RECLAIM`.
|
||||
|
||||
Для повторного использования выбираются не самые старые, а
|
||||
самые новые страницы из доступных. За счет этого цикл
|
||||
использования страниц всегда имеет минимальную длину и не
|
||||
зависит от общего числа выделенных страниц.
|
||||
|
||||
В результате механизмы кэширования и обратной записи работают с
|
||||
максимально возможной эффективностью. В случае использования
|
||||
контроллера дисков или системы хранения с
|
||||
[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
|
||||
многократное увеличение производительности по записи
|
||||
(обновлению данных).
|
||||
|
||||
2. Обработчик `OOM-KICK`.
|
||||
|
||||
Посредством `mdbx_env_set_oomfunc()` может быть установлен
|
||||
внешний обработчик (callback), который будет вызван при
|
||||
исчерпания свободных страниц из-за долгой операцией чтения.
|
||||
Обработчику будет передан PID и pthread_id. В свою очередь
|
||||
обработчик может предпринять одно из действий:
|
||||
|
||||
* отправить сигнал kill (#9), если долгое чтение выполняется
|
||||
сторонним процессом;
|
||||
|
||||
* отменить или перезапустить проблемную операцию чтения, если
|
||||
операция выполняется одним из потоков текущего процесса;
|
||||
|
||||
* подождать некоторое время, в расчете что проблемная операция
|
||||
чтения будет штатно завершена;
|
||||
|
||||
* перервать текущую операцию изменения данных с возвратом кода
|
||||
ошибки.
|
||||
|
||||
3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.
|
||||
|
||||
При работе в режиме `WRITEMAP+MAPSYNC` запись измененных
|
||||
страниц выполняется ядром ОС, что имеет ряд преимуществ. Так
|
||||
например, при крахе приложения, ядро ОС сохранит все изменения.
|
||||
|
||||
Однако, при аварийном отключении питания или сбое в ядре ОС, на
|
||||
диске будет сохранена только часть измененных страниц БД. При
|
||||
этом с большой вероятностью может оказаться так, что будут
|
||||
сохранены мета-страницы со ссылками на страницы с новыми
|
||||
версиями данных, но не сами новые данные. В этом случае БД
|
||||
будет безвозвратна разрушена, даже если до аварии производилась
|
||||
полная синхронизация данных (посредством `mdb_env_sync()`).
|
||||
|
||||
В _libmdbx_ эта проблема устранена путем полной переработки
|
||||
пути записи данных:
|
||||
|
||||
* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
|
||||
мета-страницы непосредственно, а поддерживает их теневые копии
|
||||
с переносом изменений после фиксации данных.
|
||||
|
||||
* При завершении транзакций, в зависимости от состояния
|
||||
синхронности данных между диском и оперативной память,
|
||||
_libmdbx_ помечает точки фиксации либо как сильные (strong),
|
||||
либо как слабые (weak). Так например, в режиме
|
||||
`WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
|
||||
слабые, а при явной синхронизации данных как сильные.
|
||||
|
||||
* При открытии БД выполняется автоматический откат к последней
|
||||
сильной фиксации. Этим обеспечивается гарантия сохранности БД.
|
||||
|
||||
К сожалению, такая гарантия надежности не дается бесплатно. Для
|
||||
сохранности данных, страницы формирующие крайний снимок с
|
||||
сильной фиксацией, не должны повторно использоваться
|
||||
(перезаписываться) до формирования следующей сильной точки
|
||||
фиксации. Таким образом, крайняя точка фиксации создает
|
||||
описанный выше эффект "долгого чтения". Разница же здесь в том,
|
||||
что при исчерпании свободных страниц ситуация будет
|
||||
автоматически исправлена, посредством записи изменений на диск
|
||||
и формированием новой сильной точки фиксации.
|
||||
|
||||
В последующих версиях _libmdbx_ будут предусмотрены средства
|
||||
для асинхронной записи данных на диск с автоматическим
|
||||
формированием сильных точек фиксации.
|
||||
|
||||
4. Возможность автоматического формирования контрольных точек
|
||||
(сброса данных на диск) при накоплении заданного объёма изменений,
|
||||
устанавливаемого функцией `mdbx_env_set_syncbytes()`.
|
||||
|
||||
5. Возможность получить отставание текущей транзакции чтения от
|
||||
последней версии данных в БД посредством `mdbx_txn_straggler()`.
|
||||
|
||||
6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для
|
||||
обхода всех страниц БД.
|
||||
|
||||
7. Управление отладкой и получение отладочных сообщений посредством
|
||||
`mdbx_setup_debug()`.
|
||||
|
||||
8. Возможность связать с каждой завершаемой транзакцией до 3
|
||||
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
|
||||
в транзакции чтения посредством `mdbx_canary_get()`.
|
||||
|
||||
9. Возможность узнать есть ли за текущей позицией курсора строка данных
|
||||
посредством `mdbx_cursor_eof()`.
|
||||
|
||||
10. Возможность явно запросить обновление существующей записи, без
|
||||
создания новой посредством флажка `MDB_CURRENT` для `mdbx_put()`.
|
||||
|
||||
11. Возможность обновить или удалить запись с получением предыдущего
|
||||
значения данных посредством `mdbx_replace()`.
|
||||
|
||||
12. Поддержка ключей и значений нулевой длины. Включая сортированные
|
||||
дубликаты, в том числе вне зависимости от порядка их добавления или
|
||||
обновления.
|
||||
|
||||
13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
|
||||
количество дубликатов для всех типов таблиц и любого положения курсора.
|
||||
|
||||
14. Возможность открыть БД в эксклюзивном режиме посредством
|
||||
`mdbx_env_open_ex()`, например в целях её проверки.
|
||||
|
||||
15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
|
||||
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.
|
||||
|
||||
16. Возможность получить посредством `mdbx_env_info()` дополнительную
|
||||
информацию, включая номер самой старой версии БД (снимка данных),
|
||||
который используется одним из читателей.
|
||||
|
||||
17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
|
||||
аргумент `data` для таблиц без дубликатов (без флажка `MDB_DUPSORT`), а
|
||||
при его ненулевом значении всегда использует его для сверки с удаляемой
|
||||
записью.
|
||||
|
||||
18. Возможность открыть dbi-таблицу, одновременно с установкой
|
||||
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.
|
||||
|
||||
19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
|
||||
некоторый ключ или данные в "грязной" странице БД. Таким образом избегаю
|
||||
лишнего копирования данных перед выполнением модифицирующих операций
|
||||
(значения в размещенные "грязных" страницах могут быть перезаписаны при
|
||||
изменениях, иначе они будут неизменны).
|
||||
|
||||
20. Корректное обновление текущей записи, в том числе сортированного
|
||||
дубликата, при использовании режима `MDB_CURRENT` в `mdbx_cursor_put()`.
|
||||
|
||||
21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
|
||||
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
|
||||
ОСВОБОЖДАТЬСЯ ЯВНО.
|
||||
>
|
||||
> ## _ВАЖНО_, Обратите внимание!
|
||||
>
|
||||
> Это единственное изменение в API, которое значимо меняет
|
||||
> семантику управления курсорами и может приводить к утечкам
|
||||
> памяти. Следует отметить, что это изменение вынужденно.
|
||||
> Так устраняется неоднозначность с массой тяжких последствий:
|
||||
>
|
||||
> - обращение к уже освобожденной памяти;
|
||||
> - попытки повторного освобождения памяти;
|
||||
> - memory corruption and segfaults.
|
||||
|
||||
22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
|
||||
`mdbx_put()` и `mdbx_replace()` при попытке выполнять неоднозначное
|
||||
обновление или удаления одного из нескольких значений с одним ключом,
|
||||
т.е. когда невозможно однозначно идентифицировать одно целевое значение
|
||||
из нескольких.
|
||||
|
||||
23. Возможность посредством `mdbx_get_ex()` получить значение по
|
||||
заданному ключу, одновременно с количеством дубликатов.
|
||||
|
||||
24. Наличие функций mdbx_cursor_on_first() и mdbx_cursor_on_last(),
|
||||
которые позволяют быстро выяснить стоит ли курсор на первой/последней
|
||||
позиции.
|
||||
|
||||
25. При завершении читающих транзакций, открытые в них DBI-хендлы не
|
||||
закрываются и не теряются при завершении таких транзакций посредством
|
||||
mdb_txn_abort() или mdb_txn_reset(). Что позволяет избавится от ряда
|
||||
сложно обнаруживаемых ошибок.
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
65
lmdb.h
65
lmdb.h
@ -1,7 +1,7 @@
|
||||
/** @file lmdb.h
|
||||
* @brief Reliable Lightning memory-mapped database library
|
||||
* @brief Extended Lightning memory-mapped database library
|
||||
*
|
||||
* @mainpage Reliable Lightning Memory-Mapped Database Manager (MDBX)
|
||||
* @mainpage Extended Lightning Memory-Mapped Database (MDBX)
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
* MDBX is a Btree-based database management library modeled loosely on the
|
||||
@ -66,6 +66,11 @@
|
||||
* This does not use actual memory or disk space, but users may need
|
||||
* to understand the difference so they won't be scared off.
|
||||
*
|
||||
* - An LMDB configuration will often reserve considerable \b unused
|
||||
* memory address space and maybe file size for future growth.
|
||||
* This does not use actual memory or disk space, but users may need
|
||||
* to understand the difference so they won't be scared off.
|
||||
*
|
||||
* - By default, in versions before 0.9.10, unused portions of the data
|
||||
* file might receive garbage data from memory freed by other code.
|
||||
* (This does not happen when using the #MDB_WRITEMAP flag.) As of
|
||||
@ -120,17 +125,17 @@
|
||||
* @author Leonid Yuriev, 'ReOpen' initiative <https://github.com/ReOpen>.
|
||||
* Howard Chu, Symas Corp. All rights reserved.
|
||||
*
|
||||
* @copyright 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* 2011-2016 Howard Chu, Symas Corp. All rights reserved.
|
||||
* @copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* 2011-2017 Howard Chu, Symas Corp. All rights reserved.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* @par Derived From:
|
||||
* This code is derived from LMDB engine written by Howard Chu, Symas Corporation.
|
||||
*
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp. All rights reserved.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
@ -204,7 +209,7 @@ typedef int mdb_filehandle_t;
|
||||
MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH)
|
||||
|
||||
/** The release date of this library version */
|
||||
#define MDB_VERSION_DATE "2016-06-09"
|
||||
#define MDB_VERSION_DATE "2017-02-17"
|
||||
|
||||
/** A stringifier for the version info */
|
||||
#define MDB_VERSTR(a,b,c,d) "MDBX " #a "." #b "." #c ": (" d ", https://github.com/ReOpen/libmdbx)"
|
||||
@ -349,7 +354,8 @@ typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *rel
|
||||
* For mdb_cursor_del: remove all duplicate data items.
|
||||
*/
|
||||
#define MDB_NODUPDATA 0x20
|
||||
/** For mdb_cursor_put: overwrite the current key/data pair */
|
||||
/** For mdb_cursor_put: overwrite the current key/data pair
|
||||
* MDBX allows this flag for mdb_put() for explicit overwrite/update without insertion. */
|
||||
#define MDB_CURRENT 0x40
|
||||
/** For put: Just reserve space for data, don't copy it. Return a
|
||||
* pointer to the reserved space.
|
||||
@ -1033,8 +1039,16 @@ size_t mdb_txn_id(MDB_txn *txn);
|
||||
*
|
||||
* The transaction handle is freed. It and its cursors must not be used
|
||||
* again after this call, except with #mdb_cursor_renew().
|
||||
* @note Earlier documentation incorrectly said all cursors would be freed.
|
||||
*
|
||||
* @note MDBX-mode:
|
||||
* A cursor must be closed explicitly always, before
|
||||
* or after its transaction ends. It can be reused with
|
||||
* #mdb_cursor_renew() before finally closing it.
|
||||
*
|
||||
* @note LMDB-compatible mode:
|
||||
* Earlier documentation incorrectly said all cursors would be freed.
|
||||
* Only write-transactions free cursors.
|
||||
*
|
||||
* @param[in] txn A transaction handle returned by #mdb_txn_begin()
|
||||
* @return A non-zero error value on failure and 0 on success. Some possible
|
||||
* errors are:
|
||||
@ -1051,8 +1065,16 @@ int mdb_txn_commit(MDB_txn *txn);
|
||||
*
|
||||
* The transaction handle is freed. It and its cursors must not be used
|
||||
* again after this call, except with #mdb_cursor_renew().
|
||||
* @note Earlier documentation incorrectly said all cursors would be freed.
|
||||
*
|
||||
* @note MDBX-mode:
|
||||
* A cursor must be closed explicitly always, before
|
||||
* or after its transaction ends. It can be reused with
|
||||
* #mdb_cursor_renew() before finally closing it.
|
||||
*
|
||||
* @note LMDB-compatible mode:
|
||||
* Earlier documentation incorrectly said all cursors would be freed.
|
||||
* Only write-transactions free cursors.
|
||||
*
|
||||
* @param[in] txn A transaction handle returned by #mdb_txn_begin()
|
||||
*/
|
||||
int mdb_txn_abort(MDB_txn *txn);
|
||||
@ -1144,8 +1166,9 @@ int mdb_txn_renew(MDB_txn *txn);
|
||||
* This flag may only be used in combination with #MDB_DUPSORT. This option
|
||||
* tells the library that the data items for this database are all the same
|
||||
* size, which allows further optimizations in storage and retrieval. When
|
||||
* all data items are the same size, the #MDB_GET_MULTIPLE and #MDB_NEXT_MULTIPLE
|
||||
* cursor operations may be used to retrieve multiple items at once.
|
||||
* all data items are the same size, the #MDB_GET_MULTIPLE, #MDB_NEXT_MULTIPLE
|
||||
* and #MDB_PREV_MULTIPLE cursor operations may be used to retrieve multiple
|
||||
* items at once.
|
||||
* <li>#MDB_INTEGERDUP
|
||||
* This option specifies that duplicate data items are binary integers,
|
||||
* similar to #MDB_INTEGERKEY keys.
|
||||
@ -1380,12 +1403,20 @@ int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data,
|
||||
/** @brief Delete items from a database.
|
||||
*
|
||||
* This function removes key/data pairs from the database.
|
||||
*
|
||||
* MDBX-mode:
|
||||
* The data parameter is NOT ignored regardless the database does
|
||||
* support sorted duplicate data items or not. If the data parameter
|
||||
* is non-NULL only the matching data item will be deleted.
|
||||
*
|
||||
* LMDB-compatible mode:
|
||||
* If the database does not support sorted duplicate data items
|
||||
* (#MDB_DUPSORT) the data parameter is ignored.
|
||||
* If the database supports sorted duplicates and the data parameter
|
||||
* is NULL, all of the duplicate data items for the key will be
|
||||
* deleted. Otherwise, if the data parameter is non-NULL
|
||||
* only the matching data item will be deleted.
|
||||
*
|
||||
* This function will return #MDB_NOTFOUND if the specified key/data
|
||||
* pair is not in the database.
|
||||
* @param[in] txn A transaction handle returned by #mdb_txn_begin()
|
||||
@ -1407,6 +1438,13 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
|
||||
* A cursor cannot be used when its database handle is closed. Nor
|
||||
* when its transaction has ended, except with #mdb_cursor_renew().
|
||||
* It can be discarded with #mdb_cursor_close().
|
||||
*
|
||||
* MDBX-mode:
|
||||
* A cursor must be closed explicitly always, before
|
||||
* or after its transaction ends. It can be reused with
|
||||
* #mdb_cursor_renew() before finally closing it.
|
||||
*
|
||||
* LMDB-compatible mode:
|
||||
* A cursor in a write-transaction can be closed before its transaction
|
||||
* ends, and will otherwise be closed when its transaction ends.
|
||||
* A cursor in a read-only transaction must be closed explicitly, before
|
||||
@ -1414,6 +1452,7 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
|
||||
* #mdb_cursor_renew() before finally closing it.
|
||||
* @note Earlier documentation said that cursors in every transaction
|
||||
* were closed when the transaction committed or aborted.
|
||||
*
|
||||
* @param[in] txn A transaction handle returned by #mdb_txn_begin()
|
||||
* @param[in] dbi A database handle returned by #mdb_dbi_open()
|
||||
* @param[out] cursor Address where the new #MDB_cursor handle will be stored
|
||||
|
35
mdb_chk.c
35
mdb_chk.c
@ -1,8 +1,8 @@
|
||||
/* mdbx_chk.c - memory-mapped database check tool */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
*
|
||||
* This file is part of libmdbx.
|
||||
*
|
||||
@ -52,7 +52,7 @@ flagbit dbflags[] = {
|
||||
|
||||
static volatile sig_atomic_t gotsignal;
|
||||
|
||||
static void signal_hanlder( int sig ) {
|
||||
static void signal_handler( int sig ) {
|
||||
(void) sig;
|
||||
gotsignal = 1;
|
||||
}
|
||||
@ -264,7 +264,7 @@ static int pgvisitor(size_t pgno, unsigned pgnumber, void* ctx, const char* dbi,
|
||||
problem_add("page", pgno, "illegal header-length", "%zu < %i < %zu",
|
||||
sizeof(long), header_bytes, stat.base.ms_psize - sizeof(long));
|
||||
if (payload_bytes < 1) {
|
||||
if (nentries > 0) {
|
||||
if (nentries > 1) {
|
||||
problem_add("page", pgno, "zero size-of-entry", "payload %i bytes, %i entries",
|
||||
payload_bytes, nentries);
|
||||
if ((size_t) header_bytes + unused_bytes < page_size) {
|
||||
@ -432,7 +432,6 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent)
|
||||
fflush(NULL);
|
||||
}
|
||||
skipped_subdb++;
|
||||
mdbx_dbi_close(env, dbi);
|
||||
return MDB_SUCCESS;
|
||||
}
|
||||
|
||||
@ -444,14 +443,12 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent)
|
||||
rc = mdbx_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) {
|
||||
error(" - mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc));
|
||||
mdbx_dbi_close(env, dbi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = mdbx_stat(txn, dbi, &ms, sizeof(ms));
|
||||
if (rc) {
|
||||
error(" - mdbx_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
|
||||
mdbx_dbi_close(env, dbi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -475,7 +472,6 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent)
|
||||
rc = mdbx_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
error(" - mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc));
|
||||
mdbx_dbi_close(env, dbi);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -491,9 +487,7 @@ static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent)
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
if (key.mv_size == 0) {
|
||||
problem_add("entry", record_count, "key with zero length", NULL);
|
||||
} else if (key.mv_size > maxkeysize) {
|
||||
if (key.mv_size > maxkeysize) {
|
||||
problem_add("entry", record_count, "key length exceeds max-key-size",
|
||||
"%zu > %zu", key.mv_size, maxkeysize);
|
||||
} else if ((flags & MDB_INTEGERKEY)
|
||||
@ -565,7 +559,6 @@ bailout:
|
||||
}
|
||||
|
||||
mdbx_cursor_close(mc);
|
||||
mdbx_dbi_close(env, dbi);
|
||||
return rc || problems_count;
|
||||
}
|
||||
|
||||
@ -660,13 +653,13 @@ int main(int argc, char *argv[])
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, signal_hanlder);
|
||||
signal(SIGPIPE, signal_handler);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, signal_hanlder);
|
||||
signal(SIGHUP, signal_handler);
|
||||
#endif
|
||||
signal(SIGINT, signal_hanlder);
|
||||
signal(SIGTERM, signal_hanlder);
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
envname = argv[optind];
|
||||
print("Running mdbx_chk for '%s' in %s mode...\n",
|
||||
@ -686,7 +679,11 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
maxkeysize = rc;
|
||||
|
||||
mdbx_env_set_maxdbs(env, 3);
|
||||
rc = mdbx_env_set_maxdbs(env, MAX_DBI);
|
||||
if (rc < 0) {
|
||||
error("mdbx_env_set_maxdbs failed, error %d %s\n", rc, mdbx_strerror(rc));
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
rc = mdbx_env_open_ex(env, envname, envflags, 0664, &exclusive);
|
||||
if (rc) {
|
||||
@ -747,7 +744,7 @@ int main(int argc, char *argv[])
|
||||
meta_lt(info.me_meta1_txnid, info.me_meta1_sign,
|
||||
info.me_meta2_txnid, info.me_meta2_sign) ? "tail" : "head");
|
||||
if (info.me_meta1_txnid > info.base.me_last_txnid)
|
||||
print(", rolled-back %zu (%zu >>> %zu)\n",
|
||||
print(", rolled-back %zu (%zu >>> %zu)",
|
||||
info.me_meta1_txnid - info.base.me_last_txnid,
|
||||
info.me_meta1_txnid, info.base.me_last_txnid);
|
||||
print("\n");
|
||||
@ -757,7 +754,7 @@ int main(int argc, char *argv[])
|
||||
meta_lt(info.me_meta2_txnid, info.me_meta2_sign,
|
||||
info.me_meta1_txnid, info.me_meta1_sign) ? "tail" : "head");
|
||||
if (info.me_meta2_txnid > info.base.me_last_txnid)
|
||||
print(", rolled-back %zu (%zu >>> %zu)\n",
|
||||
print(", rolled-back %zu (%zu >>> %zu)",
|
||||
info.me_meta2_txnid - info.base.me_last_txnid,
|
||||
info.me_meta2_txnid, info.base.me_last_txnid);
|
||||
print("\n");
|
||||
|
@ -1,4 +1,6 @@
|
||||
.\" Copyright 2012-2016 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDB_COPY 1 "2014/06/20" "LMDB 0.9.14"
|
||||
.SH NAME
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2012-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2012-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright (c) 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copyright 2014-2016 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDB_DUMP 1 "2014/06/20" "LMDB 0.9.14"
|
||||
.SH NAME
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,6 +1,8 @@
|
||||
.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14"
|
||||
.\" Copyright 2014-2016 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14"
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -252,7 +252,8 @@ badend:
|
||||
c2 += 2;
|
||||
}
|
||||
} else {
|
||||
c1++; c2++;
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright (c) 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copyright 2012-2016 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.TH MDB_STAT 1 "2014/06/20" "LMDB 0.9.14"
|
||||
.SH NAME
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
447
mdbx.c
447
mdbx.c
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -118,7 +118,7 @@ mdbx_env_set_syncbytes(MDB_env *env, size_t bytes)
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
env->me_sync_threshold = bytes;
|
||||
return env->me_map ? mdb_env_sync(env, 0) : 0;
|
||||
return env->me_map ? mdb_env_sync(env, 0) : MDB_SUCCESS;
|
||||
}
|
||||
|
||||
void __cold
|
||||
@ -172,7 +172,7 @@ typedef struct mdb_walk_ctx {
|
||||
|
||||
/** Depth-first tree traversal. */
|
||||
static int __cold
|
||||
mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int deep)
|
||||
mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int deep)
|
||||
{
|
||||
MDB_page *mp;
|
||||
int rc, i, nkeys;
|
||||
@ -182,7 +182,12 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
if (pg == P_INVALID)
|
||||
return MDB_SUCCESS; /* empty db */
|
||||
|
||||
rc = mdb_page_get(ctx->mw_txn, pg, &mp, NULL);
|
||||
MDB_cursor mc;
|
||||
memset(&mc, 0, sizeof(mc));
|
||||
mc.mc_snum = 1;
|
||||
mc.mc_txn = ctx->mw_txn;
|
||||
|
||||
rc = mdb_page_get(&mc, pg, &mp, NULL);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (pg != mp->mp_p.p_pgno)
|
||||
@ -220,7 +225,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
}
|
||||
|
||||
for (align_bytes = i = 0; i < nkeys;
|
||||
align_bytes += ((payload_size + align_bytes) & 1), i++) {
|
||||
align_bytes += ((payload_size + align_bytes) & 1), i++) {
|
||||
MDB_node *node;
|
||||
|
||||
if (IS_LEAF2(mp)) {
|
||||
@ -233,15 +238,13 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
payload_size += NODESIZE + node->mn_ksize;
|
||||
|
||||
if (IS_BRANCH(mp)) {
|
||||
rc = mdb_env_walk(ctx, dbi, NODEPGNO(node), flags, deep);
|
||||
rc = mdb_env_walk(ctx, dbi, NODEPGNO(node), deep);
|
||||
if (rc)
|
||||
return rc;
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(IS_LEAF(mp));
|
||||
if (node->mn_ksize < 1)
|
||||
return MDB_CORRUPTED;
|
||||
if (node->mn_flags & F_BIGDATA) {
|
||||
MDB_page *omp;
|
||||
pgno_t *opg;
|
||||
@ -249,7 +252,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
|
||||
payload_size += sizeof(pgno_t);
|
||||
opg = NODEDATA(node);
|
||||
rc = mdb_page_get(ctx->mw_txn, *opg, &omp, NULL);
|
||||
rc = mdb_page_get(&mc, *opg, &omp, NULL);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (*opg != omp->mp_p.p_pgno)
|
||||
@ -276,8 +279,6 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
MDB_db *db = NODEDATA(node);
|
||||
char* name = NULL;
|
||||
|
||||
if (NODEDSZ(node) < 1)
|
||||
return MDB_CORRUPTED;
|
||||
if (! (node->mn_flags & F_DUPDATA)) {
|
||||
name = NODEKEY(node);
|
||||
int namelen = (char*) db - name;
|
||||
@ -285,7 +286,7 @@ mdb_env_walk(mdb_walk_ctx_t *ctx, const char* dbi, pgno_t pg, int flags, int dee
|
||||
name[namelen] = 0;
|
||||
}
|
||||
rc = mdb_env_walk(ctx, (name && name[0]) ? name : dbi,
|
||||
db->md_root, node->mn_flags & F_DUPDATA, deep + 1);
|
||||
db->md_root, deep + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
@ -313,14 +314,430 @@ mdbx_env_pgwalk(MDB_txn *txn, MDBX_pgvisitor_func* visitor, void* user)
|
||||
rc = visitor(0, 2, user, "lmdb", "meta", 2, sizeof(MDB_meta)*2, PAGEHDRSZ*2,
|
||||
(txn->mt_env->me_psize - sizeof(MDB_meta) - PAGEHDRSZ) *2);
|
||||
if (! rc)
|
||||
rc = mdb_env_walk(&ctx, "free", txn->mt_dbs[FREE_DBI].md_root, 0, 0);
|
||||
rc = mdb_env_walk(&ctx, "free", txn->mt_dbs[FREE_DBI].md_root, 0);
|
||||
if (! rc)
|
||||
rc = mdb_env_walk(&ctx, "main", txn->mt_dbs[MAIN_DBI].md_root, 0, 0);
|
||||
rc = mdb_env_walk(&ctx, "main", txn->mt_dbs[MAIN_DBI].md_root, 0);
|
||||
if (! rc)
|
||||
rc = visitor(P_INVALID, 0, user, NULL, NULL, 0, 0, 0, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary)
|
||||
{
|
||||
if (unlikely(!txn))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (unlikely(F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)))
|
||||
return EACCES;
|
||||
|
||||
if (likely(canary)) {
|
||||
txn->mt_canary.x = canary->x;
|
||||
txn->mt_canary.y = canary->y;
|
||||
txn->mt_canary.z = canary->z;
|
||||
}
|
||||
txn->mt_canary.v = txn->mt_txnid;
|
||||
|
||||
return MDB_SUCCESS;
|
||||
}
|
||||
|
||||
size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary)
|
||||
{
|
||||
if(unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
return 0;
|
||||
|
||||
if (likely(canary))
|
||||
*canary = txn->mt_canary;
|
||||
|
||||
return txn->mt_txnid;
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_first(MDB_cursor *mc)
|
||||
{
|
||||
if (unlikely(mc == NULL))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (!(mc->mc_flags & C_INITIALIZED))
|
||||
return MDBX_RESULT_FALSE;
|
||||
|
||||
unsigned i;
|
||||
for(i = 0; i < mc->mc_snum; ++i) {
|
||||
if (mc->mc_ki[i])
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_on_last(MDB_cursor *mc)
|
||||
{
|
||||
if (unlikely(mc == NULL))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (!(mc->mc_flags & C_INITIALIZED))
|
||||
return MDBX_RESULT_FALSE;
|
||||
|
||||
unsigned i;
|
||||
for(i = 0; i < mc->mc_snum; ++i) {
|
||||
unsigned nkeys = NUMKEYS(mc->mc_pg[i]);
|
||||
if (mc->mc_ki[i] < nkeys - 1)
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
|
||||
int mdbx_cursor_eof(MDB_cursor *mc)
|
||||
{
|
||||
if (unlikely(mc == NULL))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if ((mc->mc_flags & C_INITIALIZED) == 0)
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
if (mc->mc_snum == 0)
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
if ((mc->mc_flags & C_EOF)
|
||||
&& mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top]))
|
||||
return MDBX_RESULT_TRUE;
|
||||
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) {
|
||||
return a->iov_len == b->iov_len
|
||||
&& memcmp(a->iov_base, b->iov_base, a->iov_len) == 0;
|
||||
}
|
||||
|
||||
/* Позволяет обновить или удалить существующую запись с получением
|
||||
* в old_data предыдущего значения данных. При этом если new_data равен
|
||||
* нулю, то выполняется удаление, иначе обновление/вставка.
|
||||
*
|
||||
* Текущее значение может находиться в уже измененной (грязной) странице.
|
||||
* В этом случае страница будет перезаписана при обновлении, а само старое
|
||||
* значение утрачено. Поэтому исходно в old_data должен быть передан
|
||||
* дополнительный буфер для копирования старого значения.
|
||||
* Если переданный буфер слишком мал, то функция вернет -1, установив
|
||||
* old_data->iov_len в соответствующее значение.
|
||||
*
|
||||
* Для не-уникальных ключей также возможен второй сценарий использования,
|
||||
* когда посредством old_data из записей с одинаковым ключом для
|
||||
* удаления/обновления выбирается конкретная. Для выбора этого сценария
|
||||
* во flags следует одновременно указать MDB_CURRENT и MDB_NOOVERWRITE.
|
||||
* Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет
|
||||
* идентифицировать запрос такого сценария.
|
||||
*
|
||||
* Функция может быть замещена соответствующими операциями с курсорами
|
||||
* после двух доработок (TODO):
|
||||
* - внешняя аллокация курсоров, в том числе на стеке (без malloc).
|
||||
* - получения статуса страницы по адресу (знать о P_DIRTY).
|
||||
*/
|
||||
int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
|
||||
MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags)
|
||||
{
|
||||
MDB_cursor mc;
|
||||
MDB_xcursor mx;
|
||||
|
||||
if (unlikely(!key || !old_data || !txn || old_data == new_data))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (unlikely(old_data->iov_base == NULL && old_data->iov_len))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(new_data == NULL && !(flags & MDB_CURRENT)))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID)))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP|MDB_CURRENT)))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)))
|
||||
return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
|
||||
|
||||
mdb_cursor_init(&mc, txn, dbi, &mx);
|
||||
mc.mc_next = txn->mt_cursors[dbi];
|
||||
txn->mt_cursors[dbi] = &mc;
|
||||
|
||||
int rc;
|
||||
MDB_val present_key = *key;
|
||||
if (F_ISSET(flags, MDB_CURRENT | MDB_NOOVERWRITE)) {
|
||||
/* в old_data значение для выбора конкретного дубликата */
|
||||
if (unlikely(!(txn->mt_dbs[dbi].md_flags & MDB_DUPSORT))) {
|
||||
rc = EINVAL;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
/* убираем лишний бит, он был признаком запрошенного режима */
|
||||
flags -= MDB_NOOVERWRITE;
|
||||
|
||||
rc = mdbx_cursor_get(&mc, &present_key, old_data, MDB_GET_BOTH);
|
||||
if (rc != MDB_SUCCESS)
|
||||
goto bailout;
|
||||
|
||||
if (new_data) {
|
||||
/* обновление конкретного дубликата */
|
||||
if (mdbx_is_samedata(old_data, new_data))
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
goto bailout;
|
||||
#if 0 /* LY: исправлено в mdbx_cursor_put(), здесь в качестве памятки */
|
||||
MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)
|
||||
&& mc.mc_xcursor->mx_db.md_entries > 1) {
|
||||
/* Если у ключа больше одного значения, то
|
||||
* сначала удаляем найденое "старое" значение.
|
||||
*
|
||||
* Этого можно не делать, так как MDBX уже
|
||||
* обучен корректно обрабатывать такие ситуации.
|
||||
*
|
||||
* Однако, следует помнить, что в LMDB при
|
||||
* совпадении размера данных, значение будет
|
||||
* просто перезаписано с нарушением
|
||||
* упорядоченности, что сломает поиск. */
|
||||
rc = mdbx_cursor_del(&mc, 0);
|
||||
if (rc != MDB_SUCCESS)
|
||||
goto bailout;
|
||||
flags -= MDB_CURRENT;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
/* в old_data буфер для сохранения предыдущего значения */
|
||||
if (unlikely(new_data && old_data->iov_base == new_data->iov_base))
|
||||
return EINVAL;
|
||||
MDB_val present_data;
|
||||
rc = mdbx_cursor_get(&mc, &present_key, &present_data, MDB_SET_KEY);
|
||||
if (unlikely(rc != MDB_SUCCESS)) {
|
||||
old_data->iov_base = NULL;
|
||||
old_data->iov_len = rc;
|
||||
if (rc != MDB_NOTFOUND || (flags & MDB_CURRENT))
|
||||
goto bailout;
|
||||
} else if (flags & MDB_NOOVERWRITE) {
|
||||
rc = MDB_KEYEXIST;
|
||||
*old_data = present_data;
|
||||
goto bailout;
|
||||
} else {
|
||||
MDB_page *page = mc.mc_pg[mc.mc_top];
|
||||
if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) {
|
||||
if (flags & MDB_CURRENT) {
|
||||
/* для не-уникальных ключей позволяем update/delete только если ключ один */
|
||||
MDB_node *leaf = NODEPTR(page, mc.mc_ki[mc.mc_top]);
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
mdb_tassert(txn, XCURSOR_INITED(&mc) && mc.mc_xcursor->mx_db.md_entries > 1);
|
||||
if (mc.mc_xcursor->mx_db.md_entries > 1) {
|
||||
rc = MDBX_EMULTIVAL;
|
||||
goto bailout;
|
||||
}
|
||||
}
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
if (new_data && mdbx_is_samedata(&present_data, new_data)) {
|
||||
*old_data = *new_data;
|
||||
goto bailout;
|
||||
}
|
||||
/* В оригинальной LMDB фладок MDB_CURRENT здесь приведет
|
||||
* к замене данных без учета MDB_DUPSORT сортировки,
|
||||
* но здесь это в любом случае допустимо, так как мы
|
||||
* проверили что для ключа есть только одно значение. */
|
||||
} else if ((flags & MDB_NODUPDATA) && mdbx_is_samedata(&present_data, new_data)) {
|
||||
/* если данные совпадают и установлен MDB_NODUPDATA */
|
||||
rc = MDB_KEYEXIST;
|
||||
goto bailout;
|
||||
}
|
||||
} else {
|
||||
/* если данные совпадают, то ничего делать не надо */
|
||||
if (new_data && mdbx_is_samedata(&present_data, new_data)) {
|
||||
*old_data = *new_data;
|
||||
goto bailout;
|
||||
}
|
||||
flags |= MDB_CURRENT;
|
||||
}
|
||||
|
||||
if (page->mp_flags & P_DIRTY) {
|
||||
if (unlikely(old_data->iov_len < present_data.iov_len)) {
|
||||
old_data->iov_base = NULL;
|
||||
old_data->iov_len = present_data.iov_len;
|
||||
rc = MDBX_RESULT_TRUE;
|
||||
goto bailout;
|
||||
}
|
||||
memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len);
|
||||
old_data->iov_len = present_data.iov_len;
|
||||
} else {
|
||||
*old_data = present_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (likely(new_data))
|
||||
rc = mdbx_cursor_put(&mc, key, new_data, flags);
|
||||
else
|
||||
rc = mdbx_cursor_del(&mc, 0);
|
||||
|
||||
bailout:
|
||||
txn->mt_cursors[dbi] = mc.mc_next;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi,
|
||||
MDB_val *key, MDB_val *data, int* values_count)
|
||||
{
|
||||
DKBUF;
|
||||
mdb_debug("===> get db %u key [%s]", dbi, DKEY(key));
|
||||
|
||||
if (unlikely(!key || !data || !txn))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID)))
|
||||
return EINVAL;
|
||||
|
||||
if (unlikely(txn->mt_flags & MDB_TXN_BLOCKED))
|
||||
return MDB_BAD_TXN;
|
||||
|
||||
MDB_cursor mc;
|
||||
MDB_xcursor mx;
|
||||
mdb_cursor_init(&mc, txn, dbi, &mx);
|
||||
|
||||
int exact = 0;
|
||||
int rc = mdb_cursor_set(&mc, key, data, MDB_SET_KEY, &exact);
|
||||
if (unlikely(rc != MDB_SUCCESS)) {
|
||||
if (rc == MDB_NOTFOUND && values_count)
|
||||
*values_count = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (values_count) {
|
||||
*values_count = 1;
|
||||
if (mc.mc_xcursor != NULL) {
|
||||
MDB_node *leaf = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
|
||||
if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
|
||||
mdb_tassert(txn, mc.mc_xcursor == &mx
|
||||
&& (mx.mx_cursor.mc_flags & C_INITIALIZED));
|
||||
*values_count = mx.mx_db.md_entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* Функция сообщает находится ли указанный адрес в "грязной" странице у
|
||||
* заданной пишущей транзакции. В конечном счете это позволяет избавиться от
|
||||
* лишнего копирования данных из НЕ-грязных страниц.
|
||||
*
|
||||
* "Грязные" страницы - это те, которые уже были изменены в ходе пишущей
|
||||
* транзакции. Соответственно, какие-либо дальнейшие изменения могут привести
|
||||
* к перезаписи таких страниц. Поэтому все функции, выполняющие изменения, в
|
||||
* качестве аргументов НЕ должны получать указатели на данные в таких
|
||||
* страницах. В свою очередь "НЕ грязные" страницы перед модификацией будут
|
||||
* скопированы.
|
||||
*
|
||||
* Другими словами, данные из "грязных" страниц должны быть либо скопированы
|
||||
* перед передачей в качестве аргументов для дальнейших модификаций, либо
|
||||
* отвергнуты на стадии проверки корректности аргументов.
|
||||
*
|
||||
* Таким образом, функция позволяет как избавится от лишнего копирования,
|
||||
* так и выполнить более полную проверку аргументов.
|
||||
*
|
||||
* ВАЖНО: Передаваемый указатель должен указывать на начало данных. Только
|
||||
* так гарантируется что актуальный заголовок страницы будет физически
|
||||
* расположен в той-же странице памяти, в том числе для многостраничных
|
||||
* P_OVERFLOW страниц с длинными данными. */
|
||||
int mdbx_is_dirty(const MDB_txn *txn, const void* ptr)
|
||||
{
|
||||
if (unlikely(!txn))
|
||||
return EINVAL;
|
||||
|
||||
if(unlikely(txn->mt_signature != MDBX_MT_SIGNATURE))
|
||||
return MDB_VERSION_MISMATCH;
|
||||
|
||||
if (unlikely(txn->mt_flags & MDB_TXN_RDONLY))
|
||||
return MDB_BAD_TXN;
|
||||
|
||||
const MDB_env *env = txn->mt_env;
|
||||
const uintptr_t mask = ~(uintptr_t) (env->me_psize - 1);
|
||||
const MDB_page *page = (const MDB_page *) ((uintptr_t) ptr & mask);
|
||||
|
||||
/* LY: Тут не всё хорошо с абсолютной достоверностью результата,
|
||||
* так как флажок P_DIRTY в LMDB может означать не совсем то,
|
||||
* что было исходно задумано, детали см в логике кода mdb_page_touch().
|
||||
*
|
||||
* Более того, в режиме БЕЗ WRITEMAP грязные страницы выделяются через
|
||||
* malloc(), т.е. находятся вне mmap-диаппазона.
|
||||
*
|
||||
* Тем не менее, однозначно страница "не грязная" если:
|
||||
* - адрес находится внутри mmap-диаппазона и в заголовке страницы
|
||||
* нет флажка P_DIRTY, то однозначно страница "не грязная".
|
||||
* - адрес вне mmap-диаппазона и его нет среди списка "грязных" страниц.
|
||||
*/
|
||||
if (env->me_map < (char*) page) {
|
||||
const size_t used_size = env->me_psize * txn->mt_next_pgno;
|
||||
if (env->me_map + used_size > (char*) page) {
|
||||
/* страница внутри диапазона */
|
||||
if (page->mp_flags & P_DIRTY)
|
||||
return MDBX_RESULT_TRUE;
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
/* Гипотетически здесь возможна ситуация, когда указатель адресует что-то
|
||||
* в пределах mmap, но за границей распределенных страниц. Это тяжелая
|
||||
* ошибка, которой не возможно добиться без каких-то мега-нарушений.
|
||||
* Поэтому не проверяем этот случай кроме как assert-ом, ибо бестолку. */
|
||||
mdb_tassert(txn, env->me_map + env->me_mapsize > (char*) page);
|
||||
}
|
||||
/* Страница вне mmap-диаппазона */
|
||||
|
||||
if (env->me_flags & MDB_WRITEMAP)
|
||||
/* Если MDB_WRITEMAP, то результат уже ясен. */
|
||||
return MDBX_RESULT_FALSE;
|
||||
|
||||
/* Смотрим список грязных страниц у заданной транзакции. */
|
||||
MDB_ID2 *list = txn->mt_u.dirty_list;
|
||||
if (list) {
|
||||
unsigned i, n = list[0].mid;
|
||||
for (i = 1; i <= n; i++) {
|
||||
const MDB_page *dirty = list[i].mptr;
|
||||
if (dirty == page)
|
||||
return MDBX_RESULT_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* При вложенных транзакциях, страница может быть в dirty-списке
|
||||
* родительской транзакции, но в этом случае она будет скопирована перед
|
||||
* изменением в текущей транзакции, т.е. относительно заданной транзакции
|
||||
* проверяемый адрес "не грязный". */
|
||||
return MDBX_RESULT_FALSE;
|
||||
}
|
||||
|
||||
int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags,
|
||||
MDB_dbi *pdbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp)
|
||||
{
|
||||
int rc = mdbx_dbi_open(txn, name, flags, pdbi);
|
||||
if (likely(rc == MDB_SUCCESS)) {
|
||||
MDB_dbi dbi = *pdbi;
|
||||
unsigned flags = txn->mt_dbs[dbi].md_flags;
|
||||
txn->mt_dbxs[dbi].md_cmp = keycmp ? keycmp : mdbx_default_keycmp(flags);
|
||||
txn->mt_dbxs[dbi].md_dcmp = datacmp ? datacmp : mdbx_default_datacmp(flags);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* attribute support functions for Nexenta ***********************************/
|
||||
|
||||
static __inline int
|
||||
|
42
mdbx.h
42
mdbx.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -211,6 +211,44 @@ typedef int MDBX_pgvisitor_func(size_t pgno, unsigned pgnumber, void* ctx,
|
||||
const char* dbi, const char *type, int nentries,
|
||||
int payload_bytes, int header_bytes, int unused_bytes);
|
||||
int mdbx_env_pgwalk(MDB_txn *txn, MDBX_pgvisitor_func* visitor, void* ctx);
|
||||
|
||||
typedef struct mdbx_canary {
|
||||
size_t x, y, z, v;
|
||||
} mdbx_canary;
|
||||
|
||||
int mdbx_canary_put(MDB_txn *txn, const mdbx_canary* canary);
|
||||
size_t mdbx_canary_get(MDB_txn *txn, mdbx_canary* canary);
|
||||
|
||||
/* Returns:
|
||||
* - MDBX_RESULT_TRUE when no more data available
|
||||
* or cursor not positioned;
|
||||
* - MDBX_RESULT_FALSE when data available;
|
||||
* - Otherwise the error code. */
|
||||
int mdbx_cursor_eof(MDB_cursor *mc);
|
||||
|
||||
/* Returns: MDBX_RESULT_TRUE, MDBX_RESULT_FALSE or Error code. */
|
||||
int mdbx_cursor_on_first(MDB_cursor *mc);
|
||||
|
||||
/* Returns: MDBX_RESULT_TRUE, MDBX_RESULT_FALSE or Error code. */
|
||||
int mdbx_cursor_on_last(MDB_cursor *mc);
|
||||
|
||||
#define MDBX_EMULTIVAL (MDB_LAST_ERRCODE - 42)
|
||||
#define MDBX_RESULT_FALSE MDB_SUCCESS
|
||||
#define MDBX_RESULT_TRUE (-1)
|
||||
|
||||
int mdbx_replace(MDB_txn *txn, MDB_dbi dbi,
|
||||
MDB_val *key, MDB_val *new_data, MDB_val *old_data, unsigned flags);
|
||||
/* Same as mdbx_get(), but:
|
||||
* 1) if values_count is not NULL, then returns the count
|
||||
* of multi-values/duplicates for a given key.
|
||||
* 2) updates the key for pointing to the actual key's data inside DB. */
|
||||
int mdbx_get_ex(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, int* values_count);
|
||||
|
||||
int mdbx_is_dirty(const MDB_txn *txn, const void* ptr);
|
||||
|
||||
int mdbx_dbi_open_ex(MDB_txn *txn, const char *name, unsigned flags,
|
||||
MDB_dbi *dbi, MDB_cmp_func *keycmp, MDB_cmp_func *datacmp);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
6
midl.c
6
midl.c
@ -2,9 +2,9 @@
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2000-2016 The OpenLDAP Foundation.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2000-2017 The OpenLDAP Foundation.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
midl.h
6
midl.h
@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2000-2016 The OpenLDAP Foundation.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2000-2017 The OpenLDAP Foundation.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
27
mtest0.c
27
mtest0.c
@ -1,9 +1,9 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -23,6 +23,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include "mdbx.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
@ -32,6 +34,18 @@
|
||||
# define DBPATH "./testdb"
|
||||
#endif
|
||||
|
||||
void* thread_entry(void *ctx)
|
||||
{
|
||||
MDB_env *env = ctx;
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
@ -60,7 +74,7 @@ int main(int argc,char * argv[])
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_maxreaders(env, 42));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
|
||||
E(stat("/proc/self/exe", &exe_stat)?errno:0);
|
||||
@ -184,6 +198,11 @@ int main(int argc,char * argv[])
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
for(i = 0; i < 41; ++i) {
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, thread_entry, env);
|
||||
}
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
4
mtest1.c
4
mtest1.c
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
mtest2.c
6
mtest2.c
@ -1,9 +1,9 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
mtest3.c
6
mtest3.c
@ -1,9 +1,9 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
mtest4.c
6
mtest4.c
@ -1,9 +1,9 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
mtest5.c
6
mtest5.c
@ -1,9 +1,9 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
6
mtest6.c
6
mtest6.c
@ -1,9 +1,9 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2011-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2011-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
10
reopen.h
10
reopen.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -57,7 +57,7 @@
|
||||
#endif /* __must_check_result */
|
||||
|
||||
#ifndef __hot
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# define __hot __attribute__((hot, optimize("O3")))
|
||||
# elif defined(__GNUC__)
|
||||
/* cland case, just put frequently used functions in separate section */
|
||||
@ -68,7 +68,7 @@
|
||||
#endif /* __hot */
|
||||
|
||||
#ifndef __cold
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
|
||||
# define __cold __attribute__((cold, optimize("Os")))
|
||||
# elif defined(__GNUC__)
|
||||
/* cland case, just put infrequently used functions in separate section */
|
||||
@ -79,7 +79,7 @@
|
||||
#endif /* __cold */
|
||||
|
||||
#ifndef __flatten
|
||||
# if defined(NDEBUG) && (defined(__GNUC__) || defined(__clang__))
|
||||
# if defined(__OPTIMIZE__) && (defined(__GNUC__) || defined(__clang__))
|
||||
# define __flatten __attribute__((flatten))
|
||||
# else
|
||||
# define __flatten
|
||||
|
@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2012-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2012-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2012-2016 Howard Chu, Symas Corp.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2012-2017 Howard Chu, Symas Corp.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
4
wbench.c
4
wbench.c
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015,2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015,2016 Peter-Service R&D LLC.
|
||||
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015,2016 Peter-Service R&D LLC.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015 Vladimir Romanov <https://www.linkedin.com/in/vladimirromanov>, Yota Lab.
|
||||
* Copyright 2016-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015 Vladimir Romanov <https://www.linkedin.com/in/vladimirromanov>, Yota Lab.
|
||||
*
|
||||
* This file is part of libmdbx.
|
||||
*
|
||||
@ -16,7 +16,6 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sys/time.h>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright (c) 2015 Vladimir Romanov <https://www.linkedin.com/in/vladimirromanov>, Yota Lab.
|
||||
* Copyright 2016-2017 Leonid Yuriev <leo@yuriev.ru>.
|
||||
* Copyright 2015 Vladimir Romanov <https://www.linkedin.com/in/vladimirromanov>, Yota Lab.
|
||||
*
|
||||
* This file is part of libmdbx.
|
||||
*
|
||||
@ -16,7 +16,6 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sys/time.h>
|
||||
|
Loading…
x
Reference in New Issue
Block a user