RE: Hanging renders

"Garth T Kidd" <garth-OnzZ1s1DREKDegMON/[email protected]> Thu, 15 Jul 2004 01:41:18 +1000
Newsgroups gmane.comp.pythin.pyds.devel
Organization Deadly Bloody Serious
Message-ID <[email protected]>
Please find attached, sufficient changes to MeshTool such that renaming or
deleting Wiki nodes re-renders any Wiki nodes or weblog posts that had
linked to them. I don't *think* the patches require any of my modifications
to Tool. 

That should make MeshTool useful. Recapping what it does: 

* If you make a reStructuredText link in *any* PyDS tool and don't resolve
it, MeshTool will try to help by asking your tools whether they have an item
that seems to match. For example:: 

    Python_

  could end up linking to any Wiki node you happened to name Python. 

* There's no ambiguity selection yet, I'm afraid. You'll just get the first
node that answers. 

* MeshTool will remember where a link had pointed, so even if (say) the
match had been made on title but the destination's title just changed, the
link won't break. 

* If you don't mind being specific, you can link to an item via any text by
using its tool and ID. For example:: 

    I like Python_! I said so recently_. 

    .. _Python: pyds:/wiki/Python
    .. _recently: pyds:/weblog/P13

  At some stage, I'll let you combine this and the looser approach. More on
that later. 

* When rendering to the desktop, MeshTool will link to the appropriate edit
or view page rather than the cloud page. You can drill around your weblog
posts, Wiki nodes, stories, and whatever else and only leave PyDS if you hit
an external link. 

* If MeshTool can't find anything to point to, it'll link to the Wiki node's
404 page. Internally, it'll link to the Wiki tool's node creation interface.
Either way, the link will be red. Don't like the Wiki tool? You can default
to any other tool that supports the right interface -- just visit the
MeshTool itself via the right-hand-side navigation bar. 

* Any time you create a new item, MeshTool will re-render any items with
as-yet unresolved links to try to resolve them. 

-----Original Message-----
From: Garth T Kidd [mailto:garth-OnzZ1s1DREKDegMON/[email protected]] 
Sent: Thursday, 15 July 2004 1:18 AM
To: 'Garth T Kidd'; 'Georg Bauer'
Cc: 'PyDS Developer List (E-Mail)'
Subject: RE: [Pyds-dev] Hanging renders

Got the bastard! It was the call to _acquire in MeshTool.__getInboundLinks. 

Now to strip out my debugging code. I'll contribute the NoisyLocker and
LockWhiner, just in case anyone else runs into such problems. NoisyLocker
makes sure that any _acquire is matched with a corresponding _release in the
same call frame. LockWhiner sits there whining if it can't get hold of a
lock, letting you know who is sitting on it. 

-----Original Message-----
From: Garth T Kidd [mailto:garth-OnzZ1s1DREKDegMON/[email protected]]
Sent: Wednesday, 14 July 2004 6:13 PM
To: 'Georg Bauer'
Cc: 'PyDS Developer List (E-Mail)'
Subject: RE: [Pyds-dev] Hanging renders

I do the locks in the tools because it's their databases I'm reaching in and
changing. :) 

I've put in more debugging code. Something's failing and not throwing an
exception, and causing a _release to be missed. That's fine within the
thread that did it, but stops other threads from doing any work. I might
have to go to the level of maintaining a stack of _acquirers and making sure
that any _release is called by the same method as the most recent _acquire. 

-----Original Message-----
From: pyds-dev-admin-iYtK5bfT9M//Ad8WF/[email protected]
[mailto:pyds-dev-admin-iYtK5bfT9M//Ad8WF/[email protected]] On Behalf Of Georg Bauer
Sent: Wednesday, 14 July 2004 7:42 AM
To: Garth T Kidd
Cc: 'PyDS Developer List (E-Mail)'
Subject: Re: [Pyds-dev] Hanging renders

Hi!

> mesh(_acquire): waiting for lock at Tue Jul 13 16:49:24 2004

Yep, that's a hanging lock.

> I *suspect* I've either got a single thread trying to acquire the lock 
> twice, or two threads deadlocking, or one thread dying whilst it has 
> the lock. Is there any infrastructure to help me resolve this?

Ugh. There was some, but I don't think it's still fully in place. But since
you do know that it's in the mes _acquire call, you can just overload
_acquire in MeshTool and put in some debugging code and wait for the next
time the problem occurs. Since locks are related to tools, the blocking
usually comes from just missing _release() calls.

My favorite would be the index_html: you do a self._acquire outside a
try:finally: block - usually I do try:finally: blocks to acquire and
release, so that exceptions don't leave locks dangling. Another thing might
be related to your ObjectDetail class, where you rely on the tools locks -
this would do locks in foreign tools, so there might be chances for
deadlocks.

bye, Georg


_______________________________________________
Pyds-dev mailing list
Pyds-dev-iYtK5bfT9M//Ad8WF/[email protected]
http://www.westfalen.de/cgi-bin/mailman/listinfo/pyds-dev
20040715-0119-mesh.diff (application/octet-stream, 17 KB)
Index: PyDS/MeshTool.py
===================================================================
RCS file: /pyds/PyDS/PyDS/MeshTool.py,v
retrieving revision 1.25
diff -c -r1.25 MeshTool.py
*** PyDS/MeshTool.py	12 Jul 2004 21:55:10 -0000	1.25
--- PyDS/MeshTool.py	14 Jul 2004 15:24:44 -0000
***************
*** 177,184 ****
--- 177,191 ----
  		self._links = meshTool.links
  		self.refresh()
  		
+ 	def _clean(self, linkText): 
+ 		"Clean `linkText`, converting it to a regular string."
+ 		if type(linkText) == type(u''): 
+ 			return str(linkText.encode(_PyDS.documentEncoding))
+ 		return str(linkText)
+ 		
  	def refresh(self): 
  		"Refresh from the database."
+ 		clean = self._clean
  		try: 
  			self._acquire()
  			outboundLinks = {}
***************
*** 187,197 ****
  			except ValueError: 
  				links = []
  			for l in links: 
! 				outboundLinks[l.linkText] = l.destHandle
  			self._outboundLinks = outboundLinks
  		finally: 
  			self._release()
  
  	def __len__(self): 
  		"Returns number of outbound links from this item."
  		return len(self._outboundLinks)
--- 194,221 ----
  			except ValueError: 
  				links = []
  			for l in links: 
! 				outboundLinks[clean(l.linkText)] = l.destHandle
  			self._outboundLinks = outboundLinks
+ 			self._markedLinks = {}
  		finally: 
  			self._release()
  
+ 	def mark(self, linkText): 
+ 		"Mark outbound links."
+ 		if not self._outboundLinks.has_key(linkText):
+ 			raise KeyError, linkText
+ 		self._markedLinks[linkText] = 1
+ 
+ 	def purge(self): 
+ 		"Delete unmarked outbound links."
+ 		candidates = {}
+ 		for linkText in self._outboundLinks.keys(): 
+ 			candidates[linkText] = 1
+ 		for linkText in self._markedLinks.keys(): 
+ 			del candidates[linkText]
+ 		for linkText in candidates: 
+ 			del self[linkText] # => __delitem__
+ 
  	def __len__(self): 
  		"Returns number of outbound links from this item."
  		return len(self._outboundLinks)
***************
*** 202,228 ****
  
  	def has_key(self, linkText): 
  		"Returns true if this item has an oubound link with text `linkText`."
! 		if type(linkText) == type(u''):
! 			linkText = linkText.encode(_PyDS.documentEncoding)
! 		linkText = str(linkText)
  		return self._outboundLinks.has_key(key)
  
  	def __getitem__(self, linkText): 
  		"Returns handle of item pointed to by outbound link `linkText`."
! 		if type(linkText) == type(u''):
! 			linkText = linkText.encode(_PyDS.documentEncoding)
! 		linkText = str(linkText)
  		destHandle = self._outboundLinks[linkText]
  		if destHandle == 0: 
  			return None
  		else: 
! 			return ItemDetail(destHandle, meshTool=self._baseTool)
  
  	def get(self, linkText, default=None): 
  		"Return handle pointed to by `linkText`, with default."
! 		if type(linkText) == type(u''):
! 			linkText = linkText.encode(_PyDS.documentEncoding)
! 		linkText = str(linkText)
  		try: 
  			return self.__getitem__(linkText)
  		except KeyError: 
--- 226,246 ----
  
  	def has_key(self, linkText): 
  		"Returns true if this item has an oubound link with text `linkText`."
! 		linkText = self._clean(linkText)
  		return self._outboundLinks.has_key(key)
  
  	def __getitem__(self, linkText): 
  		"Returns handle of item pointed to by outbound link `linkText`."
! 		linkText = self._clean(linkText)
  		destHandle = self._outboundLinks[linkText]
  		if destHandle == 0: 
  			return None
  		else: 
! 			return ItemDetail(handle=destHandle, meshTool=self._baseTool)
  
  	def get(self, linkText, default=None): 
  		"Return handle pointed to by `linkText`, with default."
! 		linkText = self._clean(linkText)
  		try: 
  			return self.__getitem__(linkText)
  		except KeyError: 
***************
*** 240,251 ****
  	
  	def __setitem__(self, linkText, destHandle): 
  		"Asserts that this item refers to `destHandle` as `linkText`."
- 		#print "__setitem__(%s, %s)" % (repr(linkText), repr(destHandle))
  		destHandle = self.__fixHandle(destHandle)
! 		if type(linkText) == type(u''):
! 			linkText = linkText.encode(_PyDS.documentEncoding)
! 		linkText = str(linkText)
! 		#print "... linkText=%s" % repr(linkText)
  		currentDestinationHandle = self._outboundLinks.get(linkText)
  		if currentDestinationHandle == destHandle: 
  			return
--- 258,265 ----
  	
  	def __setitem__(self, linkText, destHandle): 
  		"Asserts that this item refers to `destHandle` as `linkText`."
  		destHandle = self.__fixHandle(destHandle)
! 		linkText = self._clean(linkText)
  		currentDestinationHandle = self._outboundLinks.get(linkText)
  		if currentDestinationHandle == destHandle: 
  			return
***************
*** 275,300 ****
  
  	def __delitem__(self, linkText): 
  		"Assert that the item doesn't refer outbound by `linkText`."
! 		if type(linkText) == type(u''):
! 			linkText = linkText.encode(_PyDS.documentEncoding)
! 		linkText = str(linkText)
! 		if self.get(linkText) is None: 
! 			return # could raise KeyError, but no need to be *that* unforgiving
  		del self._outboundLinks[linkText]
  		try: 
  			self._acquire()
  			spec = {
  					'originHandle': self.itemHandle, 
! 					'linkText': currentDestination 
  					}
  			# Constructed to wipe out as many as are defined; less brittle 
  			# than what I've done in __setitem__, but I can't be that nasty
  			# ALL the time.
  			while 1: 
! 				(idx, found) = self.links.locate(spec)
  				if not found: 
  					break
! 				self.links.delete(idx)
  			self._commit()	
  		finally: 
  			self._release()
--- 289,312 ----
  
  	def __delitem__(self, linkText): 
  		"Assert that the item doesn't refer outbound by `linkText`."
! 		linkText = self._clean(linkText)
! 		if not self._outboundLinks.has_key(linkText): 
! 			raise KeyError, linkText
  		del self._outboundLinks[linkText]
  		try: 
  			self._acquire()
  			spec = {
  					'originHandle': self.itemHandle, 
! 					'linkText': linkText
  					}
  			# Constructed to wipe out as many as are defined; less brittle 
  			# than what I've done in __setitem__, but I can't be that nasty
  			# ALL the time.
  			while 1: 
! 				(idx, found) = self._links.locate(spec)
  				if not found: 
  					break
! 				self._links.delete(idx)
  			self._commit()	
  		finally: 
  			self._release()
***************
*** 307,313 ****
  		linkText = str(linkText)
  		try: 
  			self._acquire()
- 			#print "setdefault(%s, %d)" % (repr(linkText), destHandle)
  			match = self._links.select(originHandle = self.itemHandle, linkText=linkText)
  			if not match: 
  				self._links.append(originHandle = self.itemHandle, 
--- 319,324 ----
***************
*** 357,367 ****
  				self._commit()
  
  			else: 
! 				self.itemHandle = self.items[idx].handle
  		finally: 
  			self._release()
  			
! 		self.links = ItemLinksDetail(self.itemHandle, meshTool)
  
  	def getCloudLinkDetails(self): 
  		"Get the cloud link details for this item."
--- 368,379 ----
  				self._commit()
  
  			else: 
! 				itemHandle = self.itemHandle = self.items[idx].handle
  		finally: 
  			self._release()
  			
! 		self.links = ItemLinksDetail(itemHandle, meshTool)
! 		self.uri = meshTool.uriForHandle(itemHandle)
  
  	def getCloudLinkDetails(self): 
  		"Get the cloud link details for this item."
***************
*** 384,389 ****
--- 396,402 ----
  		instance. The latter should have already been walked over the
  		document. `itemDetail` should be an `ItemDetail` object corresponding 
  		to the source being rendered, or None."""
+ 		PyDS.Tool.traceargs()
  		docutils.nodes.SparseNodeVisitor.__init__(self, document)
  		self.targetVisitor = targetVisitor
  		self.foundTools = toolsWithStrayLinkResolvers()
***************
*** 398,422 ****
  		self.unresolvedLinkCreators = toolCache.contextSortedUnresolvedLinkCreators(targ404)
  		self.cloud404PageRenderer = toolCache.contextSortedCloud404PageRenderer(targcreate)
  	
  	def resolveStrayLink(self, stray): 
  		"""Resolve a stray by consulting all tools capable of doing so."""
  		# First, try the cache...
! 		previousDestination = self.itemLinks.setdefault(stray, 0)
  		# side-effect of the above: sets dest to 0 if not found
! 		if previousDestination is not None: 
  			try: 
  				if _flet.desktop:
! 					return previousDestination.getDesktopDisplayLinkDetails()
  				else: 
! 					return previousDestination.getCloudLinkDetails()
  			except (KeyError, ValueError): 
! 				pass
  
  		# Gather list of potential matches. 
    		matches = []
  		for tool in self.linkTextResolvers: 
  			thisMatches = tool.getUniqueIdsMatchingLinkText(stray)
! 			matches.extend([(tool, match) for match in thisMatches])
  
  		# If we have no matches, return something appropriate. 
  		if len(matches) == 0: 
--- 411,450 ----
  		self.unresolvedLinkCreators = toolCache.contextSortedUnresolvedLinkCreators(targ404)
  		self.cloud404PageRenderer = toolCache.contextSortedCloud404PageRenderer(targcreate)
  	
+ 	# Log methods
+ 	def log(self, f, *a): self.meshTool.log(f, *a)
+ 	def logError(self, f, *a): self.meshTool.logError(f, *a)
+ 	def logVerbose(self, f, *a): self.meshTool.logVerbose(f, *a)
+ 		
+ 	def purge(self): 
+ 		"""Purge unmarked outbound links from the item for which we 
+ 		were initialised."""
+ 		self.itemLinks.purge()
+ 
  	def resolveStrayLink(self, stray): 
  		"""Resolve a stray by consulting all tools capable of doing so."""
  		# First, try the cache...
! 		prevDest = self.itemLinks.setdefault(stray, 0)
  		# side-effect of the above: sets dest to 0 if not found
! 		self.itemLinks.mark(stray)
! 
! 		if prevDest is not None: 
  			try: 
  				if _flet.desktop:
! 					return prevDest.getDesktopDisplayLinkDetails()
  				else: 
! 					return prevDest.getCloudLinkDetails()
  			except (KeyError, ValueError): 
! 				self.logError(_("Cached link destination %s:%s no longer exists."),
! 				              prevDest.homeTool, prevDest.uniqueId)
! 				self.meshTool.delItem(prevDest)
  
  		# Gather list of potential matches. 
    		matches = []
  		for tool in self.linkTextResolvers: 
  			thisMatches = tool.getUniqueIdsMatchingLinkText(stray)
! 			for match in thisMatches: 
! 				matches.append((tool, match))
  
  		# If we have no matches, return something appropriate. 
  		if len(matches) == 0: 
***************
*** 446,454 ****
  		# If we have matches, return something appropriate.
  		# TODO: this is not a good way of handling ambiguity. 
  		ambiguous = len(matches) > 1
- 		#print "%s: %s match result is %s" % ('resolveStrayLink',
- 		#                                      repr(stray), 
- 		#									  repr(matches[0]))
  		homeTool, uniqueId = matches[0]
  		destHandle = self.meshTool.addItem(homeTool, uniqueId) # won't duplicate
  		self.itemLinks[stray] = destHandle
--- 474,479 ----
***************
*** 593,602 ****
  			if itemKeys is not None: 
  				itemDetail = ItemDetail(*itemKeys)
  					
! 			# ... and finally, modify the document. 
  			targetVisitor = TargetVisitor(self.document)
  			self.document.walk(targetVisitor)
! 			self.document.walk(StrayLinkResolver(self.document, targetVisitor, itemDetail, meshTool))
  
  class MeshTool(PyDS.Tool.StandardTool):
  
--- 618,631 ----
  			if itemKeys is not None: 
  				itemDetail = ItemDetail(*itemKeys)
  					
! 			# Modify the document... 
  			targetVisitor = TargetVisitor(self.document)
  			self.document.walk(targetVisitor)
! 			slr = StrayLinkResolver(self.document, targetVisitor, itemDetail, meshTool)
! 			self.document.walk(slr)
! 
! 			# ... and purge no-longer-used outbound link entries. 
! 			slr.purge()
  
  class MeshTool(PyDS.Tool.StandardTool):
  
***************
*** 752,757 ****
--- 781,789 ----
  
  	def __decodedHandle(self, handleOrUniqueId, homeTool): 
  		"Return a handle, for methods with handleOrUniqueId, homeTool=None."
+ 		if isinstance(handleOrUniqueId, ItemDetail): 
+ 			assert not homeTool
+ 			return handleOrUniqueId.itemHandle
  		if isinstance(handleOrUniqueId, int): 
  			assert not homeTool
  			return handleOrUniqueId
***************
*** 759,766 ****
  			assert homeTool
  			if isinstance(homeTool, PyDS.Tool.StandardTool):
  				homeTool = homeTool.name
! 			return self.getItemDetailsByToolAndId(homeTool, handleOrUniqueId)
  
  	def delItem(self, handleOrUniqueId, homeTool=None, abortIfTarget=0): 
  		"""Delete an item, unless inbounds and `abortIfTarget`.
  		
--- 791,815 ----
  			assert homeTool
  			if isinstance(homeTool, PyDS.Tool.StandardTool):
  				homeTool = homeTool.name
! 			return self.getItemDetailsByToolAndId(homeTool, handleOrUniqueId)['handle']
  
+ 	def renItem(self, uniqueId, homeTool, newUniqueId): 
+ 		"""Change an item's `uniqueId`."""
+ 		try: 
+ 			self._acquire()
+ 			idx = self.items.find({
+ 				'homeTool': homeTool, 
+ 				'uniqueId': uniqueId})
+ 			if idx < 0: 
+ 				raise KeyError, (homeTool, uniqueId)
+ 			self.items[idx].uniqueId = newUniqueId
+ 			self._commit()
+ 			return newUniqueId
+ 		finally: self._release()
+ 		
+ 	def __renItem(self, uniqueId, homeTool, newUniqueId): 
+ 		"""`renItem` without acquire/release"""
+ 		
  	def delItem(self, handleOrUniqueId, homeTool=None, abortIfTarget=0): 
  		"""Delete an item, unless inbounds and `abortIfTarget`.
  		
***************
*** 770,776 ****
  			list = map of inbounds as per `getInboundLinks`."""
  		try: 
  			self._acquire()
! 			return self.__delItem(handleOrUniqueId, homeTool, abortIfTarget=0)
  		finally:
  			self._release()
  
--- 819,825 ----
  			list = map of inbounds as per `getInboundLinks`."""
  		try: 
  			self._acquire()
! 			inbounds = self.__delItem(handleOrUniqueId, homeTool, abortIfTarget=0)
  		finally:
  			self._release()
  
***************
*** 780,797 ****
  		links, items = self.links, self.items
  		inboundHandles = self.__getInboundLinks(handle)
  		if abortIfTarget and inboundHandles: 
! 			return inboundHandles
! 		while 1: 
! 			idx = links.find({'destHandle': handle})
! 			if idx < 1: 
! 				break
! 			links[idx].destHandle = 0
! 		idx = items.find({'handle': handle})
! 		if idx < 0: 
! 			return 0
! 		items.delete(idx)
! 		self._commit()
! 		return 1
  		
  	def _getItemDetailsFromRow(self, row): 
  		"Fetch item details out of a row from the items table."
--- 829,857 ----
  		links, items = self.links, self.items
  		inboundHandles = self.__getInboundLinks(handle)
  		if abortIfTarget and inboundHandles: 
! 			pass
! 		else: 
! 			# Re-target all inbound links to 0
! 			while 1: 
! 				idx = links.find({'destHandle': handle})
! 				if idx < 1: 
! 					break
! 				links[idx].destHandle = 0
! 			# Find the item entry, and delete it
! 			idx = items.find({'handle': handle})
! 			if idx < 0: 
! 				self.logVerbose(_("delItem called for non-existent handle %d!?"), handle)
! 			else: 
! 				items.delete(idx)
! 				self._commit()
! 			# Log, and re-render any items that had linked to the deleted item
! 			if inboundHandles: 
! 				self.logVerbose(_("Item deleted. Re-rendering %d item(s) that had linked to it."), 
! 					 len(inboundHandles))
! 				self.meshTool.renderItems(inboundHandles)
! 			else: 
! 				self.logVerbose(_("Item deleted. No re-rendering required."))
! 		return inboundHandles
  		
  	def _getItemDetailsFromRow(self, row): 
  		"Fetch item details out of a row from the items table."
***************
*** 869,875 ****
  		"`getInboundLinks` without acquire/release"
  		handle = self.__decodedHandle(handleOrUniqueId, homeTool)
  		inboundLinks = {}
- 		self._acquire()
  		for l in self.links.select(destHandle = handle): 
  			originMap = inboundLinks.setdefault(l.originHandle, [])
  			originMap.append(l.linkText)
--- 929,934 ----
***************
*** 1041,1048 ****
--- 1100,1117 ----
  			                if l.destHandle == 0]).keys()
  		finally: 
  			self._release()
+ 		self.logVerbose("Re-rendering %d item(s) with unresolved links", len(handles))
  		return [handles, self.renderItems(handles)]
  
+ 	def renderInbound(self, handleOrUniqueId, homeTool=None): 
+ 		"Re-render items pointing to this item."
+ 		handle = self.__decodedHandle(handleOrUniqueId, homeTool)
+ 		inbound = self.getInboundLinks(handle)
+ 		self.logVerbose("Re-rendering %d item(s) referring to handle %d", 
+ 		                len(inbound), handle)
+ 		if inbound: 
+ 			self.renderItems(inbound)
+ 			
  	def renderItems(self, handles=None): 
  		"Render items associated with the `handles`, all by default."
  		try: 
***************
*** 1055,1060 ****
--- 1124,1130 ----
  			else: 
  				handlemap = [(handle, self.items.find({'handle': handle})) 
  				             for handle in handles]
+ 			self.logVerbose(_("Re-rendering %d item(s)."), len(handlemap))
  			for handle, index in handlemap: 
  				item = self.items[index]
  				toolName = item.homeTool
***************
*** 1068,1077 ****
  						method = None
  					methods[toolName] = method
  				if method is not None: 
! 					print "mesh.renderItems: requested re-render for ", \
! 						self.__uriForHomeToolAndUniqueId(toolName, item.uniqueId)
  					method(item.uniqueId)
  					rendered.append(handle)
  			return rendered
  		finally: self._release()
  
--- 1138,1149 ----
  						method = None
  					methods[toolName] = method
  				if method is not None: 
! 					print "mesh.renderItems: requested re-render for", 
  					method(item.uniqueId)
  					rendered.append(handle)
+ 				else: 
+ 					print "mesh.renderItems: couldn't find render method for ",
+ 				print self.__uriForHomeToolAndUniqueId(toolName, item.uniqueId)
  			return rendered
  		finally: self._release()
20040715-0119-weblog.diff (application/octet-stream, 2.4 KB)
Index: PyDS/WeblogTool.py
===================================================================
RCS file: /pyds/PyDS/PyDS/WeblogTool.py,v
retrieving revision 1.121
diff -c -r1.121 WeblogTool.py
*** PyDS/WeblogTool.py	6 Jul 2004 08:30:36 -0000	1.121
--- PyDS/WeblogTool.py	14 Jul 2004 15:26:47 -0000
***************
*** 320,331 ****
  	# Render an item by its unique ID
  	def renderItemByUniqueId(self, uniqueId):
  		"""Render all pages associated with the post identified by `uniqueId`."""
! 		post  = self.getPost(uniqueId)
! 		pubtime = post['pubtime']
! 		if post['onhome']: 
! 			self.renderTimeframes(pubtime, '')
! 		for cat in post['categories']: 
! 			self.renderTimeframes(pubtime, cat)
      
  	# --------------------------------------------------------------------
  
--- 320,326 ----
  	# Render an item by its unique ID
  	def renderItemByUniqueId(self, uniqueId):
  		"""Render all pages associated with the post identified by `uniqueId`."""
! 		self.renderPost(uniqueId)
      
  	# --------------------------------------------------------------------
  
***************
*** 469,476 ****
  		finally: _flet.end()
  		return ( remote, local )
  			
! 	def refresh_redir(self, req):
! 		pid = req.getFirstValue('pid')
  		try:
  			self._acquire()
  			(res, found) = self.posts.locate({'id':pid})
--- 464,470 ----
  		finally: _flet.end()
  		return ( remote, local )
  			
! 	def renderPost(self, pid): 
  		try:
  			self._acquire()
  			(res, found) = self.posts.locate({'id':pid})
***************
*** 483,493 ****
  					self.renderTimeframes(post.pubtime, category='')
  				for cat in post.categories:
  					self.renderTimeframes(post.pubtime, category=cat.name)
! 				return req.getUrl()
  			else:
! 				return req.getUrl(errmsg=_('posting %s not found') % pid)
  		finally:
  			self._release()
  
  	def post_redir(self, req):
  		title = req.getFirstValue('ptitle')
--- 477,495 ----
  					self.renderTimeframes(post.pubtime, category='')
  				for cat in post.categories:
  					self.renderTimeframes(post.pubtime, category=cat.name)
! 				return 1
  			else:
! 				raise KeyError, pid
  		finally:
  			self._release()
+ 		
+ 	def refresh_redir(self, req):
+ 		pid = req.getFirstValue('pid')
+ 		try: 
+ 			self.renderPost(pid)
+ 			return req.getUrl()
+ 		except KeyError: 
+ 			return req.getUrl(errmsg=_('posting %s not found') % pid)
  
  	def post_redir(self, req):
  		title = req.getFirstValue('ptitle')