Idea on subroutine reentrancy and database transaction
Marc Lehmann
schmorp at schmorp.de
Tue Apr 9 00:07:12 CEST 2013
On Mon, Apr 08, 2013 at 05:11:22PM +0800, Michael Fung <mike at 3open.org> wrote:
> I need to save some data using transaction protection. (Actually
> redis: watch , multi, exec ... )
(ok, I have no clue about anyevent:::redis)
> sub do_save_to_db {
> my $data = shift;
>
> # begin a db transaction to save $data to redis db
> # check for some criteria
> # commit if ok, else do rollback logic
> }
but wouldn't anyvent:redis be event-based? then you would need to invetr
the control flow - instead of being called by redis, you could arrange for
redis to call you, for example, by using Coro's rouse_cb as callback, and
then running rouse_wait, to wait for completion.
if you do that, you could then use a standard semaphore to protect your sub:
our $save_transaction = new Coro::Semaphore;
sub do_save_to_db {
my $data = shift;
my $guard = $save_transaction->guard;
# begin a db transaction to save $data to redis db
# check for some criteria
# commit if ok, else do rollback logic
}
That will prevent other threads from proceeding through the guard line
when any thread executes the rest.
> But I am afraid the transaction will be flawed by reentrancy. Any
> idea or pointers please?
If you mean that anyevent::redis does transactions for you, and it is
tied to the session, you could either use the above, or you could create
multiple sessions, effectively, a pool of sessions, and let multiple
transactions run in parallel.
If you only need to protect against yourself, a lock as above will do.
If you need to protect against anybody else (even in another process),
then you need some external locking mechanism, e.g. by using a database
or so.
> I can only think of using a global variable to indicate we are
> running in this sub so please "wait a second and check again" using
> AE::timer. But this method seems lame X}
the way to do this without inversion of control is by havign a queue. each
time you start a job, you:
1. push it into the queue
2. if less than X jobs are running, start it
3. otherwise, do nothing
And each time you finish a job, you:
1. if any jobs are queued, run one
2. otherwise, do nothing
And each job basically just unshifts the oldest job from the queue and
executes it. Or something.
That will ensure that up to X jobs will run concurrently, without a timer or
anything.
AnyEvent:Util::fork_call, or AnyEvent::DNS::_scheduler are two functions
in AnyEvent that do something like that.
--
The choice of a Deliantra, the free code+content MORPG
-----==- _GNU_ http://www.deliantra.net
----==-- _ generation
---==---(_)__ __ ____ __ Marc Lehmann
--==---/ / _ \/ // /\ \/ / schmorp at schmorp.de
-=====/_/_//_/\_,_/ /_/\_\
More information about the anyevent
mailing list