commit 897a4d92041aabef45cfe2e5d1de692d931d8ed0 from: Sergey Bronnikov date: Sun Jul 07 08:47:17 2024 UTC molly: add a CAS-register generator The patch add a generator that produces concurrent atomic updates to a shared register. commit - d1d1722e0b50fe00258caf09f715a2703160fbdc commit + 897a4d92041aabef45cfe2e5d1de692d931d8ed0 blob - 39595320a8ac12823d56ec1c3977b98aa4fb2acb blob + 74708d3ef4936fc54882ae5b3a8ad12332da9429 --- CHANGELOG.md +++ CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http ### Added +- A CAS-register generator. + ### Changed - Bump luacheck version. blob - 9a835425d5023728e3a352f7e9ad0fdf7b5d438d blob + 80ebdc35abdf22cf218ddbf61d1f567213f9002f --- molly/tests.lua +++ molly/tests.lua @@ -69,6 +69,26 @@ -- except nil. Null values in Lua tables are represented as JSON null -- (`json.NULL`, a Lua `lightuserdata` NULL pointer) is provided for -- comparison. +-- +--### CAS-Register +-- +-- Generator produces concurrent atomic updates to a shared +-- register. Writes are assumed to be unique, but this is the only +-- constraint. +-- +-- Operations are of three forms: +-- +-- { "r", "x", 1 } denotes a read of `x` observing the value 1. +-- { "w", "x", 2 } denotes a write of `x`, settings its value to 2. +-- { "cas", "x", 2 } denotes a CAS of `x`, settings its value to 2. +-- +-- Example of history: +-- +-- { type = "invoke", f = "cas", value = { 1, 5 }, process = 0, index = 1} +-- { type = "ok", f = "fail", value = { 1, 5 }, process = 0, index = 2} +-- { type = "invoke", f = "write", value = { 2 }, process = 0, index = 3} +-- { type = "ok", f = "write", value = { 2 }, process = 0, index = 4} +-- local math = require('math') @@ -135,8 +155,81 @@ end -- @function rw_register_gen local function rw_register_gen() return gen_lib.cycle(gen_lib.iter({ op_r, op_w })) +end + +-- Function that describes a 'read' operation. +local function cas_op_r() + return setmetatable({ + f = 'read', + value = { + json.NULL, + }, + }, { + __type = '', + __tostring = function(self) + return '' + end, + }) +end + +-- Function that describes a 'write' operation. +local function cas_op_w() + return setmetatable({ + f = 'write', + value = { + math.random(1, 100), + } + }, { + __type = '', + __tostring = function(self) + return '' + end, + }) end +-- Function that describes a 'cas' operation. +local function cas_op_cas() + return setmetatable({ + f = 'cas', + value = { + math.random(1, 100), + math.random(1, 100), + } + }, { + __type = '', + __tostring = function(self) + return '' + end, + }) +end + +--- CAS (Compare-And-Set) operations generator. +-- +-- @usage +-- +-- > log = require('log') +-- > tests = require('molly.tests') +-- > for _it, v in tests.cas_register_gen() do log.info(v()) end +-- {"f":"read","value":[null]} +-- {"f":"write","value":[80]} +-- {"f":"cas","value":[70,60]} +-- {"f":"read","value":[null]} +-- {"f":"write","value":[76]} +-- {"f":"cas","value":[9,67]} +-- {"f":"read","value":[null]} +-- {"f":"write","value":[34]} +-- {"f":"cas","value":[74,43]} +-- {"f":"read","value":[null]} +-- --- +-- ... +-- +-- @return an iterator +-- +-- @function cas_register_gen +local function cas_register_gen() + return gen_lib.cycle(gen_lib.iter({ cas_op_r, cas_op_w, cas_op_cas })) +end + -- Function that describes a 'list' micro operation. local function mop_list(key_count) return { @@ -242,4 +335,5 @@ end return { list_append_gen = list_append_gen, rw_register_gen = rw_register_gen, + cas_register_gen = cas_register_gen, } blob - 5c6f8131b0a73944141e4af7301b5dcb258f2599 blob + 176082a51d2c02c36c3f6ff9a2b36654b8135d18 --- test/tests.lua +++ test/tests.lua @@ -27,7 +27,7 @@ local utils = molly.utils local seed = os.time() math.randomseed(seed) -test:plan(10) +test:plan(11) test:test('clock', function(test) test:plan(5) @@ -156,6 +156,33 @@ test:test('gen', function(test) test:ok(passed_time - timeout < eps, "gen.time_limit()") end) +test:test('tests.cas_register_gen', function(test) + test:plan(4) + + local num = 5 + local n = tests.cas_register_gen():take(5):length() + test:is(n, num, 'tests.cas_register_gen(): length') + + local gen, param, state = tests.cas_register_gen() + local _, res = gen(param, state) + res = res() + local f = res.f + local value = res.value + test:ok(f == 'cas' or + f == 'write' or + f == 'read', 'tests.cas_register_gen(): function') + test:is(type(value), 'table', 'tests.cas_register_gen(): value type') + if f == 'cas' then + test:ok(#value, 2, 'tests.cas_register_gen(): cas value') + end + if f == 'read' then + test:ok(#value, 0, 'tests.cas_register_gen(): read value') + end + if f == 'write' then + test:ok(#value, 1, 'tests.cas_register_gen(): write value') + end +end) + local IDX_MOP_TYPE = 1 local IDX_MOP_KEY = 2 local IDX_MOP_VAL = 3