[Zope-dev] PANIC(300) ZODB storage error--shouldn't happen

Jim Fulton jim@digicool.com
Mon, 17 Jan 2000 19:15:56 -0500


Ross Boylan wrote:
> 
> Here's the traceback for Error: release unlocked lock
> 
> Traceback (innermost last):
>   File V:\src\ZopeRoot\lib\python\ZPublisher\Publish.py, line 214, in
> publish_module
>   File V:\src\ZopeRoot\lib\python\ZPublisher\Publish.py, line 179, in publish
>   File V:\src\ZopeRoot\lib\python\Zope\__init__.py, line 202, in
> zpublisher_exception_hook
>     (Object: RoleManager)
>   File V:\src\ZopeRoot\lib\python\ZPublisher\Publish.py, line 169, in publish
>   File V:\src\ZopeRoot\lib\python\ZODB\Transaction.py, line 275, in commit
>   File V:\src\ZopeRoot\lib\python\ZODB\Connection.py, line 467, in tpc_finish
>   File V:\src\ZopeRoot\lib\python\ZODB\BaseStorage.py, line 202, in tpc_finish
>     (Object: V:\src\ZopeRoot/var/Data.fs)

Hm. This is odd.....

You've uncovered a bug.  ZODB doesn't correctly handle the case when 
modifications are made through two connections to the same database from the same 
transaction.  I've submitted this to the bug collector to be fixed.
 
> The traceback appears in the browser, while the error message below appears
> on the console.  After this, the application is unresponsive.

Is it really unresponsive? Or does it simply not allow you to make changes?

> The only reason I got a new connection was that I didn't know how to get
> the old one 

  someObject._p_jar

where someObject must be an object that is already in the database.

> (or if it was safe to reuse it).

Yes. See above.

> Is there a canonical way to
> get Zope's connection?  I tried again using Zope.app()._p_jar.  (I had
> thought Zope.app() returned an application wrapper, and gave up when it
> didn't).  This produces the connection, but leads to the same error.

Zope.app is an application wrapper.  Zope.app() *opens a new connection*
and fetches the application object from it.  You get the same error because, 
once again, you have a new database connection.  Just use the _p_jar attribute 
from some object that is already stored in the database.

(snip)

> Here's the code I'm running, with some junk stripped out.  The product
> creates OrgUserFolder.  Some dtml then sends that object addOrgUser.  This
> creates  a regular zope user and then an OrgUserDirectory and an OrgUser,
> and puts the latter in the former.

OK, see my comments below.  I may not be following your logic 
completely, but hopefully, my suggestions are close to the mark.

> ---------------------------------- __init__.py
> -----------------------------------------------------------------
> import OrgUser
> 
> def initialize(context):
>     try:
>         context.registerClass(
>             OrgUser.OrgUserFolder,
>             constructors=(OrgUser.manage_addUserFolder, )
>             )
> [except clause omited]
> 
> ---------------------------------
> OrgUser.py-------------------------------------------------------------------
> import Globals
> from Globals import HTMLFile, MessageDialog
> import Persistence
> from AccessControl.User import UserFolder
> import Zope     # for app for database
> 
> manage_addUserForm = HTMLFile('addUser', globals())
> 
> class OrgUser(Persistence.Persistent):
>         """Add additional information on the user"""
>         meta_type ='Org User'
>         id      = 'Organizational_User'
>         title   = 'Organizational User'
> 

You need to pass either an existing database object or a connection
to getOrgUserDirectory. We'll do the former, which requires us to
pass it to the constructor.

xxx       def __init__(self, user, email):
          def __init__(self, user, email, container):
>                 self.user = user
>                 self.email = email
xxx               getOrgUserDirectory().addUser(self)
                  getOrgUserDirectory(container).addUser(self)
> 
>         def getUserName(self):
>                 return self.user.getUserName()
> 
> def addOrgUser(name, password, confirm, email, namespace, REQUEST=None ) :
>         "To be called from dtml.  Use _ for namespace"
> [code omitted--I couldn't figure out how to call it, and it seems not to be
> active]
> ==
> # class method for OrgUserDirectory
> _my_Dir = None

You need to pass either an existing database object or a connection
to getOrgUserDirectory. We'll do the former.

> def getOrgUserDirectory():
> def getOrgUserDirectory(container):
>         "Return the root directory for OrgUsers"
>         global _my_Dir
>         if not _my_Dir :
>                 import pdb
>                 pdb.set_trace()

Get the connection from the passed in container:

xxx               conn = Zope.app()._p_jar
                  conn = container._p_jar
>                 root = conn.root()
>                 _my_Dir = root.get("OrgUser", None)
>                 if _my_Dir :
>                         print "Found directory"
>                 else:
>                         print "Creating directory"
>                         root["OrgUser"] = OrgUserDirectory()
>                         _my_Dir = root["OrgUser"]
>         return _my_Dir
> 
> 
> class OrgUserDirectory(Persistence.PersistentMapping):
> 
>         def __init__(self) :
>                 print "Creating OrgUserDirectory"
>                 Persistence.PersistentMapping.__init__(self)
> 
>         def addUser(self, orgUser):
>                 "Add indicated user to database"
>                 name = orgUser.getUserName()
>                 if self.has_key(name) :
>                         raise "You can not add an existing user"
>                 self[name] = orgUser
> 
> 
> class OrgUserFolder(UserFolder):
>     """Base class for UserFolder-like objects"""
>     meta_type='Org User Folder'
>     title    ='Organizational User Folder'
>     manage_userInfo=HTMLFile('editUser',globals())
>     def addOrgUser(acl, name, password, confirm, email, REQUEST=None ) :
>         "To be called from dtml. "
>         print "passing bound addOrgUser"
>         roles = []   # to be decided
>         domains =  []
>         result = acl._addUser(name, password, confirm, roles, domains)
>         #I think that if we don't pass request, it returns None if all well
>         if result :
>                 return result  # probably not right since management
>         user = acl.getUser(name)

Pass ourselves as container:

xxx       orgUser = OrgUser(user, email)
          orgUser = OrgUser(user, email, self)
>         return "I think we're done"
> 
> def manage_addUserFolder(self,dtself=None,REQUEST=None,**ignored):
>     """ """
>     f=OrgUserFolder()
>     self=self.this()
>     try:    self._setObject('acl_users', f)
>     except: return MessageDialog(
>                    title  ='Item Exists',
>                    message='This object already contains a User Folder',
>                    action ='%s/manage_main' % REQUEST['URL1'])
>     self.__allow_groups__=f
> 
>     if REQUEST: return self.manage_main(self,REQUEST,update_menu=1)

(snip)

Hope this helps.

Jim

--
Jim Fulton           mailto:jim@digicool.com   Python Powered!        
Technical Director   (888) 344-4332            http://www.python.org  
Digital Creations    http://www.digicool.com   http://www.zope.org    

Under US Code Title 47, Sec.227(b)(1)(C), Sec.227(a)(2)(B) This email
address may not be added to any commercial mail list with out my
permission.  Violation of my privacy with advertising or SPAM will
result in a suit for a MINIMUM of $500 damages/incident, $1500 for
repeats.