Skip to content

Only one of two concurrent transactions conflict if they use different in MsgPack (but equal by key_def) keys #10159

@mkostoevr

Description

@mkostoevr

Bug description

Since the number type had been implemented, the constraint about equality of MsgPack contents of equal tuples has been discontinued, e. g. equal keys may have value of 0 in both MP_UINT8 and MP_DOUBLE format. The same applies to the double field type.

The problem is that the MVCC engine assumes the tuple keys are only equal if they're bitwice identical. Simple solution is to compare keys using key_compare instead of memcmp.

Steps to reproduce

box.cfg{
    memtx_use_mvcc_engine = true
}

s = box.schema.create_space('s')
s:create_index('pk', {type = 'HASH', parts = {1, 'double'}})

fiber = require('fiber')

function unsigned(v)
    return v
end

function double(v)
    return require('ffi').cast('double', v)
end

function conflicting_fiber(cast_lookup_key, prefix, value)
    box.begin()

    -- This creates point hole items in different positions in the hash table
    -- depending on the cast function.
    print(prefix..'Check if {1} exists.')
    local key_exists = #s:select({cast_lookup_key(1)}) ~= 0

    if not key_exists then
        print(prefix..'No, {1} does not exist. Let others go.')
        fiber.sleep(0.25) -- Enough for WAL write of insert {1}.

        print(prefix..'Insert my unique value to abort on conflict.')
        s:insert({value})
    end

    box.commit()
end

print('[0] Create [1].')
f1 = fiber.new(conflicting_fiber, unsigned, '[1] ', 11)
f1:set_joinable(true)

print('[0] Create [2].')
f2 = fiber.new(conflicting_fiber, double, '[2] ', 22)
f2:set_joinable(true)

print('[0] Let [1] and [2] go.')
fiber.yield()

-- This only finds one point hole (one with double or unsigned key), so it only
-- conflicts one of conflicting fibers (must conflict both though).
print('[0] Inserting {1}...')
s:insert({1})

print('[0] Done, now [1] and [2] must be conflicting. The new space contents:')
print(require('yaml').encode(s:select(nil, {limit = 10})))

ok, result = f1:join() -- [0] must abort on commit.
print('[1]', ok, result)

ok, result = f2:join() -- [1] must abort on commit too, but does not.
print('[2]', ok, result)

os.exit()

Actual behavior

Only one of transactions is conflicted:

[0] Create [1].
[0] Create [2].
[0] Let [1] and [2] go.
[1] Check if {1} exists.
[1] No, {1} does not exist. Let others go.
[2] Check if {1} exists.
[2] No, {1} does not exist. Let others go.
[0] Inserting {1}...
[0] Done, now [1] and [2] must be conflicting. The new space contents:
---
- [1]
...

[2] Insert my unique value to abort on conflict.
[1] Insert my unique value to abort on conflict.
[1]	false	Transaction has been aborted by conflict
[2]	true	nil

Expected behavior

Both transactions are conflicted.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions