Hello Yann,

based on your email I created new PR which incorporates your proposed
changes. Feel free to review it.

https://github.com/apache/apr/pull/49

On Mon, Oct 2, 2023 at 2:32 PM Yann Ylavic <ylavic....@gmail.com> wrote:
>
> On Fri, Sep 29, 2023 at 3:39 PM <jor...@apache.org> wrote:
> >
> > Author: jorton
> > Date: Fri Sep 29 13:39:11 2023
> > New Revision: 1912607
> []
> > --- apr/apr/trunk/dbm/apr_dbm_lmdb.c (added)
> > +++ apr/apr/trunk/dbm/apr_dbm_lmdb.c Fri Sep 29 13:39:11 2023
> []
> > +static apr_status_t vt_lmdb_open(apr_dbm_t **pdb, const char *pathname,
> > +                                 apr_int32_t mode, apr_fileperms_t perm,
> > +                                 apr_pool_t *pool)
> > +{
> []
> > +    {
> > +        int dberr;
> > +        file.txn = NULL;
> > +        file.cursor = NULL;
> > +
> > +        if ((dberr = mdb_env_create(&file.env)) == 0) {
> > +            //XXX: properly set db size
> > +            if ((dberr = mdb_env_set_mapsize(file.env, UINT32_MAX)) == 0){
> > +                if ((dberr = mdb_env_open(file.env, pathname, dbmode | 
> > DEFAULT_ENV_FLAGS, apr_posix_perms2mode(perm))) == 0) {
> > +                    if ((dberr = mdb_txn_begin(file.env, NULL, dbmode, 
> > &file.txn)) == 0){
> > +                        if ((dberr = mdb_dbi_open(file.txn, NULL, 
> > dbi_open_flags, &file.dbi)) != 0){
> > +                            /* close the env handler */
> > +                            mdb_env_close(file.env);
> > +                        }
> > +                    }
> > +                }
> > +            }
> > +        }
> > +
> > +        if (truncate){
> > +            if ((dberr = mdb_drop(file.txn, file.dbi, 0)) != 0){
> > +                mdb_env_close(file.env);
> > +            }
> > +        }
> > +
> > +        if (dberr != 0)
> > +            return db2s(dberr);
> > +    }
>
> This seems to potentially double-close or leak "file.env" on some
> errors, maybe write it like the below instead?
>
>     dberr = mdb_env_create(&file.env);
>     if (dberr != 0) {
>         return db2s(dberr);
>     }
>     dberr = ...;
>     if (dberr == 0) {
>         dberr = ...;
>     }
>     dberr = ...;
>     if (dberr == 0) {
>         dberr = ...;
>     }
>     ...
>     if (dberr == 0) {
>         dberr = mdb_dbi_open(file.txn, NULL, dbi_open_flags, &file.dbi);
>         if (dberr == 0 && truncate) {
>             dberr = mdb_drop(file.txn, file.dbi, 0);
>             if (dberr != 0) {
>                 mdb_dbi_close(file.env, file.dbi);
>             }
>         }
>     }
>     if (dberr != 0) {
>         mdb_env_close(file.env);
>         return db2s(dberr);
>     }
>
> > +
> > +    /* we have an open database... return it */
> > +    *pdb = apr_pcalloc(pool, sizeof(**pdb));
> > +    (*pdb)->pool = pool;
> > +    (*pdb)->type = &apr_dbm_type_lmdb;
> > +    (*pdb)->file = apr_pmemdup(pool, &file, sizeof(file));
> > +
> > +    /* ### register a cleanup to close the DBM? */
> > +
> > +    return APR_SUCCESS;
> > +}
> > +
> > +static void vt_lmdb_close(apr_dbm_t *dbm)
> > +{
> > +    real_file_t *f = dbm->file;
> > +
> > +    if (f->cursor){
> > +        mdb_cursor_close(f->cursor);
> > +        f->cursor = NULL;
> > +    }
> > +
> > +    if (f->txn){
> > +       mdb_txn_commit(f->txn);
> > +       f->txn = NULL;
> > +    }
>
> Shouldn't we abort the transaction here if there was no explicit
> commit (store/del) previously?
> In any case, the docs[1] for commit/abort seem to say that both
> invalidate the ->cursor, so maybe we want to write this like:
>
>    if (f->txn){
>        mdb_txn_{commit,abort}(f->txn);
>        f->txn = NULL;
>        f->cursor = NULL;
>    }
>    if (f->cursor){
>        mdb_cursor_close(f->cursor);
>        f->cursor = NULL;
>    }
>
> ?
> [1] 
> http://www.lmdb.tech/doc/group__mdb.html#ga846fbd6f46105617ac9f4d76476f6597

I think we should commit any pending transactions in vt_lmdb_close(),
therefore I used mdb_txn_commit(). If you think I should use abort
instead, please let me know. I also reorganized the order as you
correctly pointed out.

>
> > +
> > +    mdb_dbi_close(f->env, f->dbi);
> > +    mdb_env_close(f->env);
> > +
> > +    f->env = NULL;
> > +    f->dbi = 0;
> > +}
> []
> > +static apr_status_t vt_lmdb_store(apr_dbm_t *dbm, apr_datum_t key,
> > +                                  apr_datum_t value)
> > +{
> > +    real_file_t *f = dbm->file;
> > +    int rv;
> > +    MDB_val ckey = { 0 };
> > +    MDB_val cvalue = { 0 };
> > +
> > +    ckey.mv_data = key.dptr;
> > +    ckey.mv_size = key.dsize;
> > +
> > +    cvalue.mv_data = value.dptr;
> > +    cvalue.mv_size = value.dsize;
> > +
> > +    if ((rv = mdb_put(f->txn, f->dbi, &ckey, &cvalue, 0)) == 0) {
> > +        /* commit transaction */
> > +        if (((rv = mdb_txn_commit(f->txn)) == MDB_SUCCESS)
> > +            && ((rv = mdb_txn_begin(f->env, NULL, 0, &f->txn)) == 
> > MDB_SUCCESS)) {
> > +            f->cursor = NULL;
> > +        }
>
> AIUI, f->cursor should be reset whether or not mdb_txn_begin()
> succeeds here, and f->txn if any of _commit() or _begin() fails?

You are right. IMHO, if mdb_txn_commit fails, we should not reset
f->cursor since we might want to store the value again (e.g. in case
of not enough space on disk?). But if mdb_txn_commit succeeds, we can
reset f->cursor to NULL.

>
> > +    }
> > +
> > +    /* store any error info into DBM, and return a status code. */
> > +    return set_error(dbm, rv);
> > +}
> > +
> > +static apr_status_t vt_lmdb_del(apr_dbm_t *dbm, apr_datum_t key)
> > +{
> > +    real_file_t *f = dbm->file;
> > +    int rv;
> > +    MDB_val ckey = { 0 };
> > +
> > +    ckey.mv_data = key.dptr;
> > +    ckey.mv_size = key.dsize;
> > +
> > +    if ((rv = mdb_del(f->txn, f->dbi, &ckey, NULL)) == 0) {
> > +        /* commit transaction */
> > +        if (((rv = mdb_txn_commit(f->txn)) == MDB_SUCCESS)
> > +            && ((rv = mdb_txn_begin(f->env, NULL, 0, &f->txn)) == 
> > MDB_SUCCESS)) {
> > +            f->cursor = NULL;
> > +        }
>
> Likewise for f->cursor and f->txn resetting.

Fixed too, thanks!

>
> > +    }
> > +
> > +    /* store any error info into DBM, and return a status code. */
> > +    return set_error(dbm, rv);
> > +}
> []
> > +static apr_status_t vt_lmdb_firstkey(apr_dbm_t *dbm, apr_datum_t * pkey)
> > +{
> > +    real_file_t *f = dbm->file;
> > +    MDB_val first, data;
> > +    int dberr;
> > +
> > +    if ((dberr = mdb_cursor_open(f->txn, f->dbi, &f->cursor)) == 0) {
> > +        dberr = mdb_cursor_get(f->cursor, &first, &data, MDB_FIRST);
> > +        if (dberr == MDB_NOTFOUND) {
> > +            memset(&first, 0, sizeof(first));
> > +            mdb_cursor_close(f->cursor);
> > +            f->cursor = NULL;
> > +            dberr = 0;
> > +        }
> > +    }
>
> Clear/memset "first" if mdb_cursor_open() fails too?

This makes sense, I will fix it.

>
> > +
> > +    pkey->dptr = first.mv_data;
> > +    pkey->dsize = first.mv_size;
> > +
> > +    /* store any error info into DBM, and return a status code. */
> > +    return set_error(dbm, dberr);
> > +}
> > +
> > +static apr_status_t vt_lmdb_nextkey(apr_dbm_t *dbm, apr_datum_t * pkey)
> > +{
> > +    real_file_t *f = dbm->file;
> > +    MDB_val ckey, data;
> > +    int dberr;
> > +
> > +    ckey.mv_data = pkey->dptr;
> > +    ckey.mv_size = pkey->dsize;
> > +
> > +    if (f->cursor == NULL){
> > +        return APR_EINVAL;
> > +    }
> > +
> > +    dberr = mdb_cursor_get(f->cursor, &ckey, &data, MDB_NEXT);
> > +    if (dberr == MDB_NOTFOUND) {
> > +        mdb_cursor_close(f->cursor);
> > +        f->cursor = NULL;
> > +        dberr = 0;
> > +        ckey.mv_data = NULL;
> > +        ckey.mv_size = 0;
> > +    }
> > +
> > +    pkey->dptr = ckey.mv_data;
> > +    pkey->dsize = ckey.mv_size;
> > +
> > +    /* store any error info into DBM, and return a status code. */
> > +    return set_error(dbm, dberr);
> > +}
>
>
> Regards;
> Yann.
>


--
Regards
Luboš Uhliarik

Reply via email to