[TransWarp] Input wanted: Streams, Factories, sessions, etc.

Phillip J. Eby pje at telecommunity.com
Tue Nov 26 19:52:31 EST 2002


At 02:05 PM 11/18/02 -0500, Phillip J. Eby wrote:
>>Also, in writing the above, I'm thinking that maybe we should make the
>>convenience functions a bit more convenient, and change them to
>>storage.beginTxn, etc. We already use the "txn" abreviation elsewhere
>>(txnTime, joinTxn, etc), and it shouldn't cause much/any pain to anyone
>>if we change it now, before anyone is really using it...

storage.begin()/commit()/abort() are now available as aliases for the long 
forms.


>While on that subject, we should also replace the current 'txnSvc' 
>attribute in TransactionComponents to be 'joinedTxn', and make 'txnSvc' a 
>binding.Acquire(storage.ITransactionService).  In this way, one can do 
>something like this:
>
>thing = SomeComponentClas(txnSvc=storage.TransactionService())
>storage.beginTxn(thing)
>thing.doSomething()
>storage.commitTxn(thing)

The above is now available as well; txnSvc can be passed as a keyword 
argument to any subclass of TransactionComponent, and it will supply that 
service to its child components as well.


>It would be a quick-and-dirty way to "create another TransactionService, 
>etc." for your use case above.  Perhaps we could even have a way to do this:
>
># Thing is created from given class, and given a separate transaction service,
># and a proxy to the new thing is returned
>thingProxy = storage.transacted(SomeComponentClass, args..., kwargs...)
>
># methods from the proxy do a begin and commit on the wrapped item
>thingProxy.doSomething()
>
>
>Or, more specifically:
>
>storage.transacted(
>     naming.lookup("smtp://foo.bar").session,
>     # & session args/kw, if any
>).sendmail(
>     #... sendmail args
>)
>
>I'm not sure what I think about this, since it allows separation of 
>context; i.e. if you take the proxy and do something with it, you might 
>not remember it's operating within a different transaction scope, or that 
>it transacts every method call.

The new plan to deal with this scenario is to create two new base classes: 
AutoCommitter and AutoCommitOnly.  Both will accept an 'autocommit' keyword 
argument.  AutoCommitter.__init__ will check this value, and if true, it 
will set its 'txnSvc' to a new TransactionService if one was not 
supplied.  There will be a method wrapper which can be used to wrap methods 
of AutoCommitters as "autocommitting".  Autocommitting methods will wrap 
themselves in a beginTransaction/commitTransaction pair, *if and only if* 
self.autocommit is set *and* the component's 'txnSvc' is not already in a 
transaction.  (Also, if an error occurs during execution of the method, 
abortTransaction() will be called if the wrapper issued a beginTransaction().)

This algorithm allows AutoCommitters' methods to either participate in a 
larger transaction, or be used on their own as a self-contained 
transaction.  Such methods can even call one another, without fear of 
committing or aborting too soon.

The AutoCommitOnly base class is a bit different.  It will simply *require* 
that the 'autocommit' keyword argument be supplied, and that it must have a 
true value!  The purpose of this base class is to be used with components 
whose services are inherently non-transactional.  For example, an SMTP 
mail-sending operation isn't really transactional, and can't be made so 
without a lot of overhead.  So an SMTP factory's 'session()' method could 
simply require that you pass a true value for autocommit, signalling that 
you know and intend that its behavior should be "standalone".  This allows 
code to safely use a common interface, since it won't accidentally invoke a 
non-transactional component when transactional semantics are intended/expected.

Anyway, the plan is such that a "standalone sendmail" operation might look 
like this:

naming.lookup("smtp://foo.bar").session(autocommit=1).sendmail(
     #... sendmail args go here
)

Which doesn't seem too bad.  The nice thing about this is that it's then 
interchangeable with providers such as our 'queuesmtp' package, which 
simply queues outgoing mail and supports transactions, as long as the app 
wants immediate mail sending.  If the app wants transactional mail sending, 
it doesn't set 'autocommit' in the session args, but then it can only use 
providers that support a transactional 'sendmail' function.

Naturally, these considerations will be relevant for many other kinds of 
objects.  It's also likely that in anything longer than a short script, one 
will do things more like:

class Thing(binding.Component):

     mailer = binding.bindTo('alertsMailer')

     def alert(self):
         self.mailer.session(autocommit=1).sendmail(
             #... etc.
         )

Where of course 'alertsMailer' is looked up in the default naming service 
in order to get the actual provider address.




More information about the PEAK mailing list