1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 1997,2008 Oracle. All rights reserved. 5 * 6 * $Id: ex_tpcb.c,v 12.11 2008/04/21 23:46:02 alexg Exp $ 7 */ 8 9#include <sys/types.h> 10 11#include <errno.h> 12#include <stdlib.h> 13#include <string.h> 14#include <time.h> 15 16#define NS_PER_MS 1000000 /* Nanoseconds in a millisecond */ 17#define NS_PER_US 1000 /* Nanoseconds in a microsecond */ 18#ifdef _WIN32 19#include <sys/timeb.h> 20extern int getopt(int, char * const *, const char *); 21/* Implement a basic high res timer with a POSIX interface for Windows. */ 22struct timeval { 23 time_t tv_sec; 24 long tv_usec; 25}; 26int gettimeofday(struct timeval *tv, struct timezone *tz) 27{ 28 struct _timeb now; 29 _ftime(&now); 30 tv->tv_sec = now.time; 31 tv->tv_usec = now.millitm * NS_PER_US; 32 return (0); 33} 34#else 35#include <unistd.h> 36#include <sys/time.h> 37#endif 38 39#include <db.h> 40 41typedef enum { ACCOUNT, BRANCH, TELLER } FTYPE; 42 43DB_ENV *db_init __P((const char *, const char *, int, u_int32_t)); 44int hpopulate __P((DB *, int, int, int, int)); 45int populate __P((DB *, u_int32_t, u_int32_t, int, const char *)); 46u_int32_t random_id __P((FTYPE, int, int, int)); 47u_int32_t random_int __P((u_int32_t, u_int32_t)); 48int tp_populate __P((DB_ENV *, int, int, int, int, int)); 49int tp_run __P((DB_ENV *, int, int, int, int, int)); 50int tp_txn __P((DB_ENV *, DB *, DB *, DB *, DB *, int, int, int, int)); 51 52int invarg __P((const char *, int, const char *)); 53int main __P((int, char *[])); 54int usage __P((const char *)); 55 56/* 57 * This program implements a basic TPC/B driver program. To create the 58 * TPC/B database, run with the -i (init) flag. The number of records 59 * with which to populate the account, history, branch, and teller tables 60 * is specified by the a, s, b, and t flags respectively. To run a TPC/B 61 * test, use the n flag to indicate a number of transactions to run (note 62 * that you can run many of these processes in parallel to simulate a 63 * multiuser test run). 64 */ 65#define TELLERS_PER_BRANCH 10 66#define ACCOUNTS_PER_TELLER 10000 67#define HISTORY_PER_BRANCH 2592000 68 69/* 70 * The default configuration that adheres to TPCB scaling rules requires 71 * nearly 3 GB of space. To avoid requiring that much space for testing, 72 * we set the parameters much lower. If you want to run a valid 10 TPS 73 * configuration, define VALID_SCALING. 74 */ 75#ifdef VALID_SCALING 76#define ACCOUNTS 1000000 77#define BRANCHES 10 78#define TELLERS 100 79#define HISTORY 25920000 80#endif 81 82#ifdef TINY 83#define ACCOUNTS 1000 84#define BRANCHES 10 85#define TELLERS 100 86#define HISTORY 10000 87#endif 88 89#ifdef VERY_TINY 90#define ACCOUNTS 500 91#define BRANCHES 10 92#define TELLERS 50 93#define HISTORY 5000 94#endif 95 96#if !defined(VALID_SCALING) && !defined(TINY) && !defined(VERY_TINY) 97#define ACCOUNTS 100000 98#define BRANCHES 10 99#define TELLERS 100 100#define HISTORY 259200 101#endif 102 103#define HISTORY_LEN 100 104#define RECLEN 100 105#define BEGID 1000000 106 107typedef struct _defrec { 108 u_int32_t id; 109 u_int32_t balance; 110 u_int8_t pad[RECLEN - sizeof(u_int32_t) - sizeof(u_int32_t)]; 111} defrec; 112 113typedef struct _histrec { 114 u_int32_t aid; 115 u_int32_t bid; 116 u_int32_t tid; 117 u_int32_t amount; 118 u_int8_t pad[RECLEN - 4 * sizeof(u_int32_t)]; 119} histrec; 120 121char *progname = "ex_tpcb"; /* Program name. */ 122 123int 124main(argc, argv) 125 int argc; 126 char *argv[]; 127{ 128 extern char *optarg; 129 extern int optind; 130 DB_ENV *dbenv; 131 int accounts, branches, seed, tellers, history; 132 int ch, iflag, mpool, ntxns, ret, txn_no_sync, verbose; 133 const char *home; 134 135 home = "TESTDIR"; 136 accounts = branches = history = tellers = 0; 137 iflag = mpool = ntxns = txn_no_sync = verbose = 0; 138 seed = (int)time(NULL); 139 140 while ((ch = getopt(argc, argv, "a:b:c:fh:in:S:s:t:v")) != EOF) 141 switch (ch) { 142 case 'a': /* Number of account records */ 143 if ((accounts = atoi(optarg)) <= 0) 144 return (invarg(progname, ch, optarg)); 145 break; 146 case 'b': /* Number of branch records */ 147 if ((branches = atoi(optarg)) <= 0) 148 return (invarg(progname, ch, optarg)); 149 break; 150 case 'c': /* Cachesize in bytes */ 151 if ((mpool = atoi(optarg)) <= 0) 152 return (invarg(progname, ch, optarg)); 153 break; 154 case 'f': /* Fast mode: no txn sync. */ 155 txn_no_sync = 1; 156 break; 157 case 'h': /* DB home. */ 158 home = optarg; 159 break; 160 case 'i': /* Initialize the test. */ 161 iflag = 1; 162 break; 163 case 'n': /* Number of transactions */ 164 if ((ntxns = atoi(optarg)) <= 0) 165 return (invarg(progname, ch, optarg)); 166 break; 167 case 'S': /* Random number seed. */ 168 if ((seed = atoi(optarg)) <= 0) 169 return (invarg(progname, ch, optarg)); 170 break; 171 case 's': /* Number of history records */ 172 if ((history = atoi(optarg)) <= 0) 173 return (invarg(progname, ch, optarg)); 174 break; 175 case 't': /* Number of teller records */ 176 if ((tellers = atoi(optarg)) <= 0) 177 return (invarg(progname, ch, optarg)); 178 break; 179 case 'v': /* Verbose option. */ 180 verbose = 1; 181 break; 182 case '?': 183 default: 184 return (usage(progname)); 185 } 186 argc -= optind; 187 argv += optind; 188 189 srand((u_int)seed); 190 191 /* Initialize the database environment. */ 192 if ((dbenv = db_init(home, 193 progname, mpool, txn_no_sync ? DB_TXN_NOSYNC : 0)) == NULL) 194 return (EXIT_FAILURE); 195 196 accounts = accounts == 0 ? ACCOUNTS : accounts; 197 branches = branches == 0 ? BRANCHES : branches; 198 tellers = tellers == 0 ? TELLERS : tellers; 199 history = history == 0 ? HISTORY : history; 200 201 if (verbose) 202 printf("%ld Accounts, %ld Branches, %ld Tellers, %ld History\n", 203 (long)accounts, (long)branches, 204 (long)tellers, (long)history); 205 206 if (iflag) { 207 if (ntxns != 0) 208 return (usage(progname)); 209 tp_populate(dbenv, 210 accounts, branches, history, tellers, verbose); 211 } else { 212 if (ntxns == 0) 213 return (usage(progname)); 214 tp_run(dbenv, ntxns, accounts, branches, tellers, verbose); 215 } 216 217 if ((ret = dbenv->close(dbenv, 0)) != 0) { 218 fprintf(stderr, "%s: dbenv->close failed: %s\n", 219 progname, db_strerror(ret)); 220 return (EXIT_FAILURE); 221 } 222 223 return (EXIT_SUCCESS); 224} 225 226int 227invarg(progname, arg, str) 228 const char *progname; 229 int arg; 230 const char *str; 231{ 232 (void)fprintf(stderr, 233 "%s: invalid argument for -%c: %s\n", progname, arg, str); 234 return (EXIT_FAILURE); 235} 236 237int 238usage(progname) 239 const char *progname; 240{ 241 const char *a1, *a2; 242 243 a1 = "[-fv] [-a accounts] [-b branches]\n"; 244 a2 = "\t[-c cache_size] [-h home] [-S seed] [-s history] [-t tellers]"; 245 (void)fprintf(stderr, "usage: %s -i %s %s\n", progname, a1, a2); 246 (void)fprintf(stderr, 247 " %s -n transactions %s %s\n", progname, a1, a2); 248 return (EXIT_FAILURE); 249} 250 251/* 252 * db_init -- 253 * Initialize the environment. 254 */ 255DB_ENV * 256db_init(home, prefix, cachesize, flags) 257 const char *home, *prefix; 258 int cachesize; 259 u_int32_t flags; 260{ 261 DB_ENV *dbenv; 262 u_int32_t local_flags; 263 int ret; 264 265 if ((ret = db_env_create(&dbenv, 0)) != 0) { 266 fprintf(stderr, 267 "%s: db_env_create: %s\n", progname, db_strerror(ret)); 268 return (NULL); 269 } 270 dbenv->set_errfile(dbenv, stderr); 271 dbenv->set_errpfx(dbenv, prefix); 272 (void)dbenv->set_cachesize(dbenv, 0, 273 cachesize == 0 ? 4 * 1024 * 1024 : (u_int32_t)cachesize, 0); 274 275 if (flags & (DB_TXN_NOSYNC)) 276 (void)dbenv->set_flags(dbenv, DB_TXN_NOSYNC, 1); 277 flags &= ~(DB_TXN_NOSYNC); 278 279 local_flags = flags | DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | 280 DB_INIT_MPOOL | DB_INIT_TXN; 281 if ((ret = dbenv->open(dbenv, home, local_flags, 0)) != 0) { 282 dbenv->err(dbenv, ret, "DB_ENV->open: %s", home); 283 (void)dbenv->close(dbenv, 0); 284 return (NULL); 285 } 286 return (dbenv); 287} 288 289/* 290 * Initialize the database to the specified number of accounts, branches, 291 * history records, and tellers. 292 */ 293int 294tp_populate(env, accounts, branches, history, tellers, verbose) 295 DB_ENV *env; 296 int accounts, branches, history, tellers, verbose; 297{ 298 DB *dbp; 299 u_int32_t balance, idnum, oflags; 300 u_int32_t end_anum, end_bnum, end_tnum; 301 u_int32_t start_anum, start_bnum, start_tnum; 302 int ret; 303 304 idnum = BEGID; 305 balance = 500000; 306 oflags = DB_CREATE; 307 308 if ((ret = db_create(&dbp, env, 0)) != 0) { 309 env->err(env, ret, "db_create"); 310 return (1); 311 } 312 (void)dbp->set_h_nelem(dbp, (u_int32_t)accounts); 313 314 if ((ret = dbp->open(dbp, NULL, "account", NULL, 315 DB_HASH, oflags, 0644)) != 0) { 316 env->err(env, ret, "DB->open: account"); 317 return (1); 318 } 319 320 start_anum = idnum; 321 populate(dbp, idnum, balance, accounts, "account"); 322 idnum += accounts; 323 end_anum = idnum - 1; 324 if ((ret = dbp->close(dbp, 0)) != 0) { 325 env->err(env, ret, "DB->close: account"); 326 return (1); 327 } 328 if (verbose) 329 printf("Populated accounts: %ld - %ld\n", 330 (long)start_anum, (long)end_anum); 331 332 /* 333 * Since the number of branches is very small, we want to use very 334 * small pages and only 1 key per page, i.e., key-locking instead 335 * of page locking. 336 */ 337 if ((ret = db_create(&dbp, env, 0)) != 0) { 338 env->err(env, ret, "db_create"); 339 return (1); 340 } 341 (void)dbp->set_h_ffactor(dbp, 1); 342 (void)dbp->set_h_nelem(dbp, (u_int32_t)branches); 343 (void)dbp->set_pagesize(dbp, 512); 344 if ((ret = dbp->open(dbp, NULL, "branch", NULL, 345 DB_HASH, oflags, 0644)) != 0) { 346 env->err(env, ret, "DB->open: branch"); 347 return (1); 348 } 349 start_bnum = idnum; 350 populate(dbp, idnum, balance, branches, "branch"); 351 idnum += branches; 352 end_bnum = idnum - 1; 353 if ((ret = dbp->close(dbp, 0)) != 0) { 354 env->err(env, ret, "DB->close: branch"); 355 return (1); 356 } 357 if (verbose) 358 printf("Populated branches: %ld - %ld\n", 359 (long)start_bnum, (long)end_bnum); 360 361 /* 362 * In the case of tellers, we also want small pages, but we'll let 363 * the fill factor dynamically adjust itself. 364 */ 365 if ((ret = db_create(&dbp, env, 0)) != 0) { 366 env->err(env, ret, "db_create"); 367 return (1); 368 } 369 (void)dbp->set_h_ffactor(dbp, 0); 370 (void)dbp->set_h_nelem(dbp, (u_int32_t)tellers); 371 (void)dbp->set_pagesize(dbp, 512); 372 if ((ret = dbp->open(dbp, NULL, "teller", NULL, 373 DB_HASH, oflags, 0644)) != 0) { 374 env->err(env, ret, "DB->open: teller"); 375 return (1); 376 } 377 378 start_tnum = idnum; 379 populate(dbp, idnum, balance, tellers, "teller"); 380 idnum += tellers; 381 end_tnum = idnum - 1; 382 if ((ret = dbp->close(dbp, 0)) != 0) { 383 env->err(env, ret, "DB->close: teller"); 384 return (1); 385 } 386 if (verbose) 387 printf("Populated tellers: %ld - %ld\n", 388 (long)start_tnum, (long)end_tnum); 389 390 if ((ret = db_create(&dbp, env, 0)) != 0) { 391 env->err(env, ret, "db_create"); 392 return (1); 393 } 394 (void)dbp->set_re_len(dbp, HISTORY_LEN); 395 if ((ret = dbp->open(dbp, NULL, "history", NULL, 396 DB_RECNO, oflags, 0644)) != 0) { 397 env->err(env, ret, "DB->open: history"); 398 return (1); 399 } 400 401 hpopulate(dbp, history, accounts, branches, tellers); 402 if ((ret = dbp->close(dbp, 0)) != 0) { 403 env->err(env, ret, "DB->close: history"); 404 return (1); 405 } 406 return (0); 407} 408 409int 410populate(dbp, start_id, balance, nrecs, msg) 411 DB *dbp; 412 u_int32_t start_id, balance; 413 int nrecs; 414 const char *msg; 415{ 416 DBT kdbt, ddbt; 417 defrec drec; 418 int i, ret; 419 420 kdbt.flags = 0; 421 kdbt.data = &drec.id; 422 kdbt.size = sizeof(u_int32_t); 423 ddbt.flags = 0; 424 ddbt.data = &drec; 425 ddbt.size = sizeof(drec); 426 memset(&drec.pad[0], 1, sizeof(drec.pad)); 427 428 for (i = 0; i < nrecs; i++) { 429 drec.id = start_id + (u_int32_t)i; 430 drec.balance = balance; 431 if ((ret = 432 (dbp->put)(dbp, NULL, &kdbt, &ddbt, DB_NOOVERWRITE)) != 0) { 433 dbp->err(dbp, 434 ret, "Failure initializing %s file\n", msg); 435 return (1); 436 } 437 } 438 return (0); 439} 440 441int 442hpopulate(dbp, history, accounts, branches, tellers) 443 DB *dbp; 444 int history, accounts, branches, tellers; 445{ 446 DBT kdbt, ddbt; 447 histrec hrec; 448 db_recno_t key; 449 int i, ret; 450 451 memset(&kdbt, 0, sizeof(kdbt)); 452 memset(&ddbt, 0, sizeof(ddbt)); 453 ddbt.data = &hrec; 454 ddbt.size = sizeof(hrec); 455 kdbt.data = &key; 456 kdbt.size = sizeof(key); 457 memset(&hrec.pad[0], 1, sizeof(hrec.pad)); 458 hrec.amount = 10; 459 460 for (i = 1; i <= history; i++) { 461 hrec.aid = random_id(ACCOUNT, accounts, branches, tellers); 462 hrec.bid = random_id(BRANCH, accounts, branches, tellers); 463 hrec.tid = random_id(TELLER, accounts, branches, tellers); 464 if ((ret = dbp->put(dbp, NULL, &kdbt, &ddbt, DB_APPEND)) != 0) { 465 dbp->err(dbp, ret, "dbp->put"); 466 return (1); 467 } 468 } 469 return (0); 470} 471 472u_int32_t 473random_int(lo, hi) 474 u_int32_t lo, hi; 475{ 476 u_int32_t ret; 477 int t; 478 479#ifndef RAND_MAX 480#define RAND_MAX 0x7fffffff 481#endif 482 t = rand(); 483 ret = (u_int32_t)(((double)t / ((double)(RAND_MAX) + 1)) * 484 (hi - lo + 1)); 485 ret += lo; 486 return (ret); 487} 488 489u_int32_t 490random_id(type, accounts, branches, tellers) 491 FTYPE type; 492 int accounts, branches, tellers; 493{ 494 u_int32_t min, max, num; 495 496 max = min = BEGID; 497 num = accounts; 498 switch (type) { 499 case TELLER: 500 min += branches; 501 num = tellers; 502 /* FALLTHROUGH */ 503 case BRANCH: 504 if (type == BRANCH) 505 num = branches; 506 min += accounts; 507 /* FALLTHROUGH */ 508 case ACCOUNT: 509 max = min + num - 1; 510 } 511 return (random_int(min, max)); 512} 513 514int 515tp_run(dbenv, n, accounts, branches, tellers, verbose) 516 DB_ENV *dbenv; 517 int n, accounts, branches, tellers, verbose; 518{ 519 DB *adb, *bdb, *hdb, *tdb; 520 int failed, ret, txns; 521 struct timeval start_tv, end_tv; 522 double start_time, end_time; 523 524 adb = bdb = hdb = tdb = NULL; 525 526 /* 527 * Open the database files. 528 */ 529 if ((ret = db_create(&adb, dbenv, 0)) != 0) { 530 dbenv->err(dbenv, ret, "db_create"); 531 goto err; 532 } 533 if ((ret = adb->open(adb, NULL, "account", NULL, DB_UNKNOWN, 534 DB_AUTO_COMMIT, 0)) != 0) { 535 dbenv->err(dbenv, ret, "DB->open: account"); 536 goto err; 537 } 538 if ((ret = db_create(&bdb, dbenv, 0)) != 0) { 539 dbenv->err(dbenv, ret, "db_create"); 540 goto err; 541 } 542 if ((ret = bdb->open(bdb, NULL, "branch", NULL, DB_UNKNOWN, 543 DB_AUTO_COMMIT, 0)) != 0) { 544 dbenv->err(dbenv, ret, "DB->open: branch"); 545 goto err; 546 } 547 if ((ret = db_create(&hdb, dbenv, 0)) != 0) { 548 dbenv->err(dbenv, ret, "db_create"); 549 goto err; 550 } 551 if ((ret = hdb->open(hdb, NULL, "history", NULL, DB_UNKNOWN, 552 DB_AUTO_COMMIT, 0)) != 0) { 553 dbenv->err(dbenv, ret, "DB->open: history"); 554 goto err; 555 } 556 if ((ret = db_create(&tdb, dbenv, 0)) != 0) { 557 dbenv->err(dbenv, ret, "db_create"); 558 goto err; 559 } 560 if ((ret = tdb->open(tdb, NULL, "teller", NULL, DB_UNKNOWN, 561 DB_AUTO_COMMIT, 0)) != 0) { 562 dbenv->err(dbenv, ret, "DB->open: teller"); 563 goto err; 564 } 565 566 (void)gettimeofday(&start_tv, NULL); 567 568 for (txns = n, failed = 0; n-- > 0;) 569 if ((ret = tp_txn(dbenv, adb, bdb, tdb, hdb, 570 accounts, branches, tellers, verbose)) != 0) 571 ++failed; 572 573 (void)gettimeofday(&end_tv, NULL); 574 575 start_time = start_tv.tv_sec + ((start_tv.tv_usec + 0.0)/NS_PER_MS); 576 end_time = end_tv.tv_sec + ((end_tv.tv_usec + 0.0)/NS_PER_MS); 577 if (end_time == start_time) 578 end_time += 1/NS_PER_MS; 579 580 printf("%s: %d txns: %d failed, %.3f sec, %.2f TPS\n", progname, 581 txns, failed, (end_time - start_time), 582 (txns - failed) / (double)(end_time - start_time)); 583 584err: if (adb != NULL) 585 (void)adb->close(adb, 0); 586 if (bdb != NULL) 587 (void)bdb->close(bdb, 0); 588 if (tdb != NULL) 589 (void)tdb->close(tdb, 0); 590 if (hdb != NULL) 591 (void)hdb->close(hdb, 0); 592 return (ret == 0 ? 0 : 1); 593} 594 595/* 596 * XXX Figure out the appropriate way to pick out IDs. 597 */ 598int 599tp_txn(dbenv, adb, bdb, tdb, hdb, accounts, branches, tellers, verbose) 600 DB_ENV *dbenv; 601 DB *adb, *bdb, *tdb, *hdb; 602 int accounts, branches, tellers, verbose; 603{ 604 DBC *acurs, *bcurs, *tcurs; 605 DBT d_dbt, d_histdbt, k_dbt, k_histdbt; 606 DB_TXN *t; 607 db_recno_t key; 608 defrec rec; 609 histrec hrec; 610 int account, branch, teller, ret; 611 612 t = NULL; 613 acurs = bcurs = tcurs = NULL; 614 615 /* 616 * !!! 617 * This is sample code -- we could move a lot of this into the driver 618 * to make it faster. 619 */ 620 account = random_id(ACCOUNT, accounts, branches, tellers); 621 branch = random_id(BRANCH, accounts, branches, tellers); 622 teller = random_id(TELLER, accounts, branches, tellers); 623 624 memset(&d_histdbt, 0, sizeof(d_histdbt)); 625 626 memset(&k_histdbt, 0, sizeof(k_histdbt)); 627 k_histdbt.data = &key; 628 k_histdbt.size = sizeof(key); 629 630 memset(&k_dbt, 0, sizeof(k_dbt)); 631 k_dbt.size = sizeof(int); 632 633 memset(&d_dbt, 0, sizeof(d_dbt)); 634 d_dbt.flags = DB_DBT_USERMEM; 635 d_dbt.data = &rec; 636 d_dbt.ulen = sizeof(rec); 637 638 hrec.aid = account; 639 hrec.bid = branch; 640 hrec.tid = teller; 641 hrec.amount = 10; 642 /* Request 0 bytes since we're just positioning. */ 643 d_histdbt.flags = DB_DBT_PARTIAL; 644 645 /* 646 * START PER-TRANSACTION TIMING. 647 * 648 * Technically, TPCB requires a limit on response time, you only get 649 * to count transactions that complete within 2 seconds. That's not 650 * an issue for this sample application -- regardless, here's where 651 * the transaction begins. 652 */ 653 if (dbenv->txn_begin(dbenv, NULL, &t, 0) != 0) 654 goto err; 655 656 if (adb->cursor(adb, t, &acurs, 0) != 0 || 657 bdb->cursor(bdb, t, &bcurs, 0) != 0 || 658 tdb->cursor(tdb, t, &tcurs, 0) != 0) 659 goto err; 660 661 /* Account record */ 662 k_dbt.data = &account; 663 if (acurs->get(acurs, &k_dbt, &d_dbt, DB_SET) != 0) 664 goto err; 665 rec.balance += 10; 666 if (acurs->put(acurs, &k_dbt, &d_dbt, DB_CURRENT) != 0) 667 goto err; 668 669 /* Branch record */ 670 k_dbt.data = &branch; 671 if (bcurs->get(bcurs, &k_dbt, &d_dbt, DB_SET) != 0) 672 goto err; 673 rec.balance += 10; 674 if (bcurs->put(bcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0) 675 goto err; 676 677 /* Teller record */ 678 k_dbt.data = &teller; 679 if (tcurs->get(tcurs, &k_dbt, &d_dbt, DB_SET) != 0) 680 goto err; 681 rec.balance += 10; 682 if (tcurs->put(tcurs, &k_dbt, &d_dbt, DB_CURRENT) != 0) 683 goto err; 684 685 /* History record */ 686 d_histdbt.flags = 0; 687 d_histdbt.data = &hrec; 688 d_histdbt.ulen = sizeof(hrec); 689 if (hdb->put(hdb, t, &k_histdbt, &d_histdbt, DB_APPEND) != 0) 690 goto err; 691 692 if (acurs->close(acurs) != 0 || bcurs->close(bcurs) != 0 || 693 tcurs->close(tcurs) != 0) 694 goto err; 695 696 ret = t->commit(t, 0); 697 t = NULL; 698 if (ret != 0) 699 goto err; 700 /* END PER-TRANSACTION TIMING. */ 701 702 return (0); 703 704err: if (acurs != NULL) 705 (void)acurs->close(acurs); 706 if (bcurs != NULL) 707 (void)bcurs->close(bcurs); 708 if (tcurs != NULL) 709 (void)tcurs->close(tcurs); 710 if (t != NULL) 711 (void)t->abort(t); 712 713 if (verbose) 714 printf("Transaction A=%ld B=%ld T=%ld failed\n", 715 (long)account, (long)branch, (long)teller); 716 return (-1); 717} 718