Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SubTxn order matters -> MDB_BAD_TXN #30

Open
akotlar opened this issue Jan 3, 2018 · 0 comments
Open

SubTxn order matters -> MDB_BAD_TXN #30

akotlar opened this issue Jan 3, 2018 · 0 comments

Comments

@akotlar
Copy link
Contributor

akotlar commented Jan 3, 2018

Issue:

If you create a parent transaction using LMDB::Txn->new(), then a child transaction using Env->BeginTxn(), no problem.

The reverse order utterly breaks: using the parent transaction causes MDB_BAD_TXN error.

Not certain if this is an issue with my understanding, or LMDB_File.

I've attached a test case, and below

Edit: Similar issues arise with read_only transactions, except it becomes impossible to have two simultaneous transactions. This seems strange to me: as LMDB transactions are isolated there should be no issue with N transactions per thread.

  • I think this is actually correct: I believe LMDB does not allow any read-only sub transactions.
    • NO_TLS should remove this restriction
  • Is there a way to issue several, non-sub transactions, without NO_TLS.
  • Is the problem that only one transaction per thread can have access to one dbi (like a file handle) at a time?
use 5.10.0;
use strict;
use warnings;
package DBManager;
use LMDB_File qw/:all/;
my %envs;

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 

sub dbPut {
  my ($self,$dbName, $key, $data, $skipCommit) = @_;

  # 0 to create database if not found
  my $db = $self->_getDbi($dbName);

  if(!$db->{db}->Alive) {
    $db->{db}->Txn = $db->{env}->BeginTxn();
    # not strictly necessary, but I am concerned about hard to trace abort bugs related to scope
    $db->{db}->Txn->AutoCommit(1);
  }

  $db->{db}->Txn->put($db->{dbi}, $key, $data);

  $db->{db}->Txn->commit() unless $skipCommit;

  if($LMDB_File::last_err) {
    if($LMDB_File::last_err != MDB_KEYEXIST) {
      die $LMDB_File::last_err;
    }

    #reset the class error variable, to avoid crazy error reporting later
    $LMDB_File::last_err = 0;
  }

  return 0;
}

sub dbReadOne {
  my ($self, $dbName, $key, $skipCommit) = @_;

  my $db = $self->_getDbi($dbName) or return undef;

  if(!$db->{db}->Alive) {
    $db->{db}->Txn = $db->{env}->BeginTxn();
    # not strictly necessary, but I am concerned about hard to trace abort bugs related to scope
    $db->{db}->Txn->AutoCommit(1);
  }

  $db->{db}->Txn->get($db->{dbi}, $key, my $data);

  # Commit unless the user specifically asks not to
  #if(!$skipCommit) {
  $db->{db}->Txn->commit() unless $skipCommit;

  if($LMDB_File::last_err) {
    if($LMDB_File::last_err != MDB_NOTFOUND ) {
      die $LMDB_File::last_err;
    }

    $LMDB_File::last_err = 0;
  }

  return $data;
}


sub dbStartCursorTxn {
  my ($self, $dbName) = @_;

  my $db = $self->_getDbi($dbName) or return;

  my $txn = $db->{env}->BeginTxn();

  # Help LMDB_File track our cursor
  LMDB::Cursor::open($txn, $db->{dbi}, my $cursor);

  # Unsafe, private LMDB_File method access but Cursor::open does not track cursors
  $LMDB::Txn::Txns{$$txn}{Cursors}{$$cursor} = 1;

  return [$txn, $cursor];
}

sub _getDbi {
  # Exists and not defined, because in read only database we may discover
  # that some chromosomes don't have any data (example: hg38 refSeq chrM)
  

  #   $_[0]  $_[1], $_[2]
  # Don't create used by dbGetNumberOfEntries
  my ($self, $dbPath) = @_;

  if ($envs{$dbPath}) {
    return $envs{$dbPath};
  }

  my $env = LMDB::Env->new($dbPath, {
    mapsize => 128 * 1024 * 1024 * 1024, # Plenty space, don't worry
    #maxdbs => 20, # Some databases
    mode   => 0600,
    maxdbs => 0, # Some databases; else we get a MDB_DBS_FULL error (max db limit reached)
  });

  if(! $env ) {
    die 'No env';
  }

  my $txn = $env->BeginTxn();

  my $dbFlags;

  my $DB = $txn->OpenDB(undef, MDB_INTEGERKEY);

  # ReadMode 1 gives memory pointer for perf reasons, not safe
  $DB->ReadMode(1);

  if($LMDB_File::last_err) {
    die $LMDB_File::last_err;
  }

  # Now db is open
  my $err = $txn->commit();

  if($err) {
    die $err;
  }

  $envs{$dbPath} = {env => $env, dbi => $DB->dbi, db => $DB};

  return $envs{$dbPath};
}

1;

use Test::More;
use DDP;
my $db = DBManager->new();

my $dbIdx = 1;
my $pos = 99;
my $val = "HELLO WORLD";

system('rm -rf ./test && mkdir ./test');

#### WORKS GREAT ####
my $cursor;
$cursor = $db->dbStartCursorTxn('test');

### Test Unsafe Transactions (Manually Managed) ##########
$db->dbPut('test', $pos, [], 1);

$db->dbReadOne('test', $pos);

p %LMDB::Env::Envs;

$db->dbReadOne('test', $pos);
undef $db;
undef $cursor;

system('rm -rf ./test && mkdir ./test');

$db = DBManager->new();
#### DIES MISERABLE DEATH ####
say "The reverse order doesn't work";

$db->dbPut('test', $pos, [], 1);
$cursor = $db->dbStartCursorTxn('test');
$db->dbReadOne('test', $pos);

say "We will never see this";

subtxn_bug.pl.zip

@akotlar akotlar changed the title SubTxn order is confusing SubTxn order matters -> MDB_BAD_TXN Jan 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant