You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Howard <hl...@gmail.com> on 2010/07/15 01:30:46 UTC

[Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Tapestry applications are inherently stateful: during and between
requests, information in Tapestry components, value stored in fields,
stick around. This is a great thing: it lets you program a web
application in a sensible way, using stateful objects full of mutable
properties and methods to operate on those properties.
It also has its downside: Tapestry has to maintain a pool of page
instances. And in Tapestry, page instances are big: a tree of hundreds
or perhaps thousands of interrelated objects: the tree of Tapestry
structural objects that forms the basic page structure, the component
and mixin objects hanging off that tree, the binding objects that
connect parameters of components to properties of their containing
component, the template objects that represents elements and content
from component templates, and many, many more that most Tapestry
developers are kept unawares of.
This has proven to be a problem with biggest and busiest sites
constructed using Tapestry. Keeping a pool of those objects, checking
them in and out, and discarded them when no longer needed is draining
needed resources, especially heap space.
So that seems like an irreconcilable problem eh? Removing mutable state
from pages and components would turn Tapestry into something else
entirely. On the other hand, allowing mutable state means that
applications, especially big complex applications with many pages,
become memory hogs.
I suppose one approach would be to simply create a page instance for
the duration of a request, and discard it at the end. However, page
construction in Tapestry is very complicated and although some effort
was expended in Tapestry 5.1 to reduce the cost of page construction,
it is still present. Additionally, Tapestry is full of small
optimizations that improve performance ... assuming a page is reused
over time. Throwing away pages is a non-starter.
So we're back to square one ... we can't eliminate mutable state, but
(for large applications) we can't live with it either.
Tapestry has already been down this route: the way persistent fields
are handled gives the illusion that the page is kept around between
requests. You might think that Tapestry serializes the page and stores
the whole thing in the session. In reality, Tapestry is shuffling just
the individual persistent field values in to and out of the HttpSessio.
To both the end user and the Tapestry developer, it feels like the
entire page is live between requests, but it's a bit of a shell game,
providing an equivalent page instance that has the same values in its
fields.
What's going on in trunk right now is extrapolating that concept from
persistent fields to all mutable fields. Every access to every mutable
field in a Tapestry page is converted, as part of the class
transformation process, into an access against a per-thread Map of keys
and values. The end result is that a single page instance can be used
across threads without any synchronization issues and without any
conflicts. Each thread has its own per-thread Map.
This idea was suggested in years past, but the APIs to accomplish it
(as well as the necessary meta-programming savvy) just wasn't
available. However, as a side effect of rewriting and simplifying the
class transformation APIs in 5.2, it became very reasonable to do this.
Let's take an important example: handling typical, mutable fields. This
is the responsibility of the UnclaimedFieldWorker class, part of
Tapestry component class transformation pipeline. UnclaimedFieldWorker
finds fields that have not be "claimed" by some other part of the
pipeline and converts them to read and write their values to the
per-thread Map. A claimed field may store an injected service, asset or
component, or be a component parameter.
public class UnclaimedFieldWorker implements
ComponentClassTransformWorker { private final PerthreadManager
perThreadManager; private final ComponentClassCache classCache; static
class UnclaimedFieldConduit implements FieldValueConduit { private
final InternalComponentResources resources; private final
PerThreadValue<Object> fieldValue; // Set prior to the
containingPageDidLoad lifecycle event private Object fieldDefaultValue;
private UnclaimedFieldConduit(InternalComponentResources resources,
PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
this.resources = resources; this.fieldValue = fieldValue;
this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
public void set(Object newValue) { fieldValue.set(newValue); // This
catches the case where the instance initializer method sets a value for
the field. // That value is captured and used when no specific value
has been stored. if (!resources.isLoaded()) fieldDefaultValue =
newValue; } } public UnclaimedFieldWorker(ComponentClassCache
classCache, PerthreadManager perThreadManager) { this.classCache =
classCache; this.perThreadManager = perThreadManager; } public void
transform(ClassTransformation transformation, MutableComponentModel
model) { for (TransformField field :
transformation.matchUnclaimedFields()) { transformField(field); } }
private void transformField(TransformField field) { int modifiers =
field.getModifiers(); if (Modifier.isFinal(modifiers) ||
Modifier.isStatic(modifiers)) return;
ComponentValueProvider<FieldValueConduit> provider =
createFieldValueConduitProvider(field);
field.replaceAccess(provider); } private
ComponentValueProvider<FieldValueConduit>
createFieldValueConduitProvider(TransformField field) { final String
fieldName = field.getName(); final String fieldType = field.getType();
return new ComponentValueProvider<FieldValueConduit>() { public
FieldValueConduit get(ComponentResources resources) { Object
fieldDefaultValue = classCache.defaultValueForType(fieldType); String
key = String.format("UnclaimedFieldWorker:%s/%s",
resources.getCompleteId(), fieldName); return new
UnclaimedFieldConduit((InternalComponentResources) resources,
perThreadManager.createValue(key), fieldDefaultValue); } }; } }

That seems like a lot, but lets break it down bit by bit.
public void transform(ClassTransformation transformation,
MutableComponentModel model) { for (TransformField field :
transformation.matchUnclaimedFields()) { transformField(field); } }
private void transformField(TransformField field) { int modifiers =
field.getModifiers(); if (Modifier.isFinal(modifiers) ||
Modifier.isStatic(modifiers)) return;
ComponentValueProvider<FieldValueConduit> provider =
createFieldValueConduitProvider(field); field.replaceAccess(provider); }
The transform() method is the lone method for this class, as defined by
ComponentClassTransformWorker. It uses a method on the
ClassTransformation to locate all the unclaimed fields. TransformField
is the representation of a field of a component class during the
transformation process. As we'll see it is very easy to intercept
access to the field.
Some of those fields are final or static and are just ignored. A
ComponentValueProvider is a callback object: when the component
(whatever it is) is first instantiated, the provider will be invoked
and the return value stored into a new field. A FieldValueConduit is an
object that takes over responsibility for access to a TransformField:
internally, all read and write access to the field is passed through
the conduit object.
So, what we're saying is: when the component is first created, use the
callback to create a conduit, and change any read or write access to
the field to pass through the created conduit. If a component is
instantiated multiple times (either in different pages, or within the
same page) each instance of the component will end up with a specific
FieldValueConduit.
Fine so far; it comes down to what's inside the
createFieldValueConduitProvider() method:
private ComponentValueProvider<FieldValueConduit>
createFieldValueConduitProvider(TransformField field) { final String
fieldName = field.getName(); final String fieldType = field.getType();
return new ComponentValueProvider<FieldValueConduit>() { public
FieldValueConduit get(ComponentResources resources) { Object
fieldDefaultValue = classCache.defaultValueForType(fieldType); String
key = String.format("UnclaimedFieldWorker:%s/%s",
resources.getCompleteId(), fieldName); return new
UnclaimedFieldConduit((InternalComponentResources) resources,
perThreadManager.createValue(key), fieldDefaultValue); } }; }

Here we capture the name of the field and its type (expressed as
String). Inside the get() method we determine the initial default value
for the field: typically just null, but may be 0 (for a primitive
numeric field) or false (for a primitive boolean field).
Next we build a unique key used to store and retrieve the field's value
inside the per-thread Map. The key includes the complete id of the
component and the name of the field: thus two different component
instances, in the same page or across different pages, will have their
own unique key.
We use the PerthreadManager service to create a PerThreadValue for the
field.
Lastly, we create the conduit object. Let's look at the conduit in more
detail:
static class UnclaimedFieldConduit implements FieldValueConduit {
private final InternalComponentResources resources; private final
PerThreadValue<Object> fieldValue; // Set prior to the
containingPageDidLoad lifecycle event private Object fieldDefaultValue;
private UnclaimedFieldConduit(InternalComponentResources resources,
PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
this.resources = resources; this.fieldValue = fieldValue;
this.fieldDefaultValue = fieldDefaultValue; }

We use the special InternalComponentResources interface because we'll
need to know if the page is loading, or in normal operation (that's
coming up). We capture our initial guess at a default value for the
field (remember: null, false or 0) but that may change.
public Object get() { return fieldValue.exists() ? fieldValue.get() :
fieldDefaultValue; }

Whenever code inside the component reads the field, this method will be
invoked. It checks to see if a value has been stored into the
PerThreadValue object this request; if so the stored value is returned,
otherwise the field default value is returned.
Notice the distinction here between null and no value at all. Just
because the field is set to null doesn't mean we should switch over the
the default value (assuming the default is not null).
The last hurdle is updates to the field:
public void set(Object newValue) { fieldValue.set(newValue); // This
catches the case where the instance initializer method sets a value for
the field. // That value is captured and used when no specific value
has been stored. if (!resources.isLoaded()) fieldDefaultValue =
newValue; }

The basic logic is just to stuff the new value into the PerThreadValue.
However, there's one special case: a field initialization (whether it's
in the component's constructor, or where the field is defined) turns
into a call to set(). We can differentiate because that update occurs
before the page is marked as fully loaded, rather than in normal use of
the page.
And that's it! Now, to be honest, this is more detail than a typical
Tapestry developer ever needs to know. However, it's a good
demonstration of how Tapestry's class transformation APIs make Java
code fluid; capable of being changed dynamically (under carefully
controlled circumstances).
Back to pooling: how is this going to affect performance? That's an
open question, and putting together a performance testing environment
is another task at the top of my list. My suspicion is that the new
overhead will not make a visible difference for small applications
(dozens of pages, reasonable number of concurrent users) ... but for
high end sites (hundreds of pages, large numbre of concurrent users)
the avoidance of pooling and page construction will make a big
difference!

--
Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM

RE: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Jim O'Callaghan <jc...@yahoo.co.uk>.
This looks very interesting Howard.  Is this something that is going into
T5.2.0 sometime soon or is it already present?

Regards,
Jim.

-----Original Message-----
From: Howard [mailto:hlship@gmail.com] 
Sent: 15 July 2010 00:31
To: users@tapestry.apache.org
Subject: [Tapestry Central] Everyone out of the Pool! Tapestry goes
singleton!

Tapestry applications are inherently stateful: during and between
requests, information in Tapestry components, value stored in fields,
stick around. This is a great thing: it lets you program a web
application in a sensible way, using stateful objects full of mutable
properties and methods to operate on those properties.
It also has its downside: Tapestry has to maintain a pool of page
instances. And in Tapestry, page instances are big: a tree of hundreds
or perhaps thousands of interrelated objects: the tree of Tapestry
structural objects that forms the basic page structure, the component
and mixin objects hanging off that tree, the binding objects that
connect parameters of components to properties of their containing
component, the template objects that represents elements and content
from component templates, and many, many more that most Tapestry
developers are kept unawares of.
This has proven to be a problem with biggest and busiest sites
constructed using Tapestry. Keeping a pool of those objects, checking
them in and out, and discarded them when no longer needed is draining
needed resources, especially heap space.
So that seems like an irreconcilable problem eh? Removing mutable state
from pages and components would turn Tapestry into something else
entirely. On the other hand, allowing mutable state means that
applications, especially big complex applications with many pages,
become memory hogs.
I suppose one approach would be to simply create a page instance for
the duration of a request, and discard it at the end. However, page
construction in Tapestry is very complicated and although some effort
was expended in Tapestry 5.1 to reduce the cost of page construction,
it is still present. Additionally, Tapestry is full of small
optimizations that improve performance ... assuming a page is reused
over time. Throwing away pages is a non-starter.
So we're back to square one ... we can't eliminate mutable state, but
(for large applications) we can't live with it either.
Tapestry has already been down this route: the way persistent fields
are handled gives the illusion that the page is kept around between
requests. You might think that Tapestry serializes the page and stores
the whole thing in the session. In reality, Tapestry is shuffling just
the individual persistent field values in to and out of the HttpSessio.
To both the end user and the Tapestry developer, it feels like the
entire page is live between requests, but it's a bit of a shell game,
providing an equivalent page instance that has the same values in its
fields.
What's going on in trunk right now is extrapolating that concept from
persistent fields to all mutable fields. Every access to every mutable
field in a Tapestry page is converted, as part of the class
transformation process, into an access against a per-thread Map of keys
and values. The end result is that a single page instance can be used
across threads without any synchronization issues and without any
conflicts. Each thread has its own per-thread Map.
This idea was suggested in years past, but the APIs to accomplish it
(as well as the necessary meta-programming savvy) just wasn't
available. However, as a side effect of rewriting and simplifying the
class transformation APIs in 5.2, it became very reasonable to do this.
Let's take an important example: handling typical, mutable fields. This
is the responsibility of the UnclaimedFieldWorker class, part of
Tapestry component class transformation pipeline. UnclaimedFieldWorker
finds fields that have not be "claimed" by some other part of the
pipeline and converts them to read and write their values to the
per-thread Map. A claimed field may store an injected service, asset or
component, or be a component parameter.
public class UnclaimedFieldWorker implements
ComponentClassTransformWorker { private final PerthreadManager
perThreadManager; private final ComponentClassCache classCache; static
class UnclaimedFieldConduit implements FieldValueConduit { private
final InternalComponentResources resources; private final
PerThreadValue<Object> fieldValue; // Set prior to the
containingPageDidLoad lifecycle event private Object fieldDefaultValue;
private UnclaimedFieldConduit(InternalComponentResources resources,
PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
this.resources = resources; this.fieldValue = fieldValue;
this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
public void set(Object newValue) { fieldValue.set(newValue); // This
catches the case where the instance initializer method sets a value for
the field. // That value is captured and used when no specific value
has been stored. if (!resources.isLoaded()) fieldDefaultValue =
newValue; } } public UnclaimedFieldWorker(ComponentClassCache
classCache, PerthreadManager perThreadManager) { this.classCache =
classCache; this.perThreadManager = perThreadManager; } public void
transform(ClassTransformation transformation, MutableComponentModel
model) { for (TransformField field :
transformation.matchUnclaimedFields()) { transformField(field); } }
private void transformField(TransformField field) { int modifiers =
field.getModifiers(); if (Modifier.isFinal(modifiers) ||
Modifier.isStatic(modifiers)) return;
ComponentValueProvider<FieldValueConduit> provider =
createFieldValueConduitProvider(field);
field.replaceAccess(provider); } private
ComponentValueProvider<FieldValueConduit>
createFieldValueConduitProvider(TransformField field) { final String
fieldName = field.getName(); final String fieldType = field.getType();
return new ComponentValueProvider<FieldValueConduit>() { public
FieldValueConduit get(ComponentResources resources) { Object
fieldDefaultValue = classCache.defaultValueForType(fieldType); String
key = String.format("UnclaimedFieldWorker:%s/%s",
resources.getCompleteId(), fieldName); return new
UnclaimedFieldConduit((InternalComponentResources) resources,
perThreadManager.createValue(key), fieldDefaultValue); } }; } }

That seems like a lot, but lets break it down bit by bit.
public void transform(ClassTransformation transformation,
MutableComponentModel model) { for (TransformField field :
transformation.matchUnclaimedFields()) { transformField(field); } }
private void transformField(TransformField field) { int modifiers =
field.getModifiers(); if (Modifier.isFinal(modifiers) ||
Modifier.isStatic(modifiers)) return;
ComponentValueProvider<FieldValueConduit> provider =
createFieldValueConduitProvider(field); field.replaceAccess(provider); }
The transform() method is the lone method for this class, as defined by
ComponentClassTransformWorker. It uses a method on the
ClassTransformation to locate all the unclaimed fields. TransformField
is the representation of a field of a component class during the
transformation process. As we'll see it is very easy to intercept
access to the field.
Some of those fields are final or static and are just ignored. A
ComponentValueProvider is a callback object: when the component
(whatever it is) is first instantiated, the provider will be invoked
and the return value stored into a new field. A FieldValueConduit is an
object that takes over responsibility for access to a TransformField:
internally, all read and write access to the field is passed through
the conduit object.
So, what we're saying is: when the component is first created, use the
callback to create a conduit, and change any read or write access to
the field to pass through the created conduit. If a component is
instantiated multiple times (either in different pages, or within the
same page) each instance of the component will end up with a specific
FieldValueConduit.
Fine so far; it comes down to what's inside the
createFieldValueConduitProvider() method:
private ComponentValueProvider<FieldValueConduit>
createFieldValueConduitProvider(TransformField field) { final String
fieldName = field.getName(); final String fieldType = field.getType();
return new ComponentValueProvider<FieldValueConduit>() { public
FieldValueConduit get(ComponentResources resources) { Object
fieldDefaultValue = classCache.defaultValueForType(fieldType); String
key = String.format("UnclaimedFieldWorker:%s/%s",
resources.getCompleteId(), fieldName); return new
UnclaimedFieldConduit((InternalComponentResources) resources,
perThreadManager.createValue(key), fieldDefaultValue); } }; }

Here we capture the name of the field and its type (expressed as
String). Inside the get() method we determine the initial default value
for the field: typically just null, but may be 0 (for a primitive
numeric field) or false (for a primitive boolean field).
Next we build a unique key used to store and retrieve the field's value
inside the per-thread Map. The key includes the complete id of the
component and the name of the field: thus two different component
instances, in the same page or across different pages, will have their
own unique key.
We use the PerthreadManager service to create a PerThreadValue for the
field.
Lastly, we create the conduit object. Let's look at the conduit in more
detail:
static class UnclaimedFieldConduit implements FieldValueConduit {
private final InternalComponentResources resources; private final
PerThreadValue<Object> fieldValue; // Set prior to the
containingPageDidLoad lifecycle event private Object fieldDefaultValue;
private UnclaimedFieldConduit(InternalComponentResources resources,
PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
this.resources = resources; this.fieldValue = fieldValue;
this.fieldDefaultValue = fieldDefaultValue; }

We use the special InternalComponentResources interface because we'll
need to know if the page is loading, or in normal operation (that's
coming up). We capture our initial guess at a default value for the
field (remember: null, false or 0) but that may change.
public Object get() { return fieldValue.exists() ? fieldValue.get() :
fieldDefaultValue; }

Whenever code inside the component reads the field, this method will be
invoked. It checks to see if a value has been stored into the
PerThreadValue object this request; if so the stored value is returned,
otherwise the field default value is returned.
Notice the distinction here between null and no value at all. Just
because the field is set to null doesn't mean we should switch over the
the default value (assuming the default is not null).
The last hurdle is updates to the field:
public void set(Object newValue) { fieldValue.set(newValue); // This
catches the case where the instance initializer method sets a value for
the field. // That value is captured and used when no specific value
has been stored. if (!resources.isLoaded()) fieldDefaultValue =
newValue; }

The basic logic is just to stuff the new value into the PerThreadValue.
However, there's one special case: a field initialization (whether it's
in the component's constructor, or where the field is defined) turns
into a call to set(). We can differentiate because that update occurs
before the page is marked as fully loaded, rather than in normal use of
the page.
And that's it! Now, to be honest, this is more detail than a typical
Tapestry developer ever needs to know. However, it's a good
demonstration of how Tapestry's class transformation APIs make Java
code fluid; capable of being changed dynamically (under carefully
controlled circumstances).
Back to pooling: how is this going to affect performance? That's an
open question, and putting together a performance testing environment
is another task at the top of my list. My suspicion is that the new
overhead will not make a visible difference for small applications
(dozens of pages, reasonable number of concurrent users) ... but for
high end sites (hundreds of pages, large numbre of concurrent users)
the avoidance of pooling and page construction will make a big
difference!

--
Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by "Thiago H. de Paula Figueiredo" <th...@gmail.com>.
On Thu, 15 Jul 2010 16:28:35 -0300, Chuck Kring <cj...@pacbell.net>  
wrote:

Hi, Chuck!

> Our application consists of several dashboard pages that are used like a  
> network management console works:  the pages automatically refresh as  
> things change, users can click on icons to drill down, etc.   The page  
> relies heavily on Ajax calls to update icons and charts

Nice observation! You're absolutely right. This remebered me of this  
discussion about reverse AJAX (Comet) in Tapestry:  
http://old.nabble.com/comet-implementation-issue-to28531634s302.html#a28570596.  
The scenario described in the first message of the discussion isn't an  
issue without the pool. The implementation Rajesh did would work without  
problems in the poolless Tapestry. :)

-- 
Thiago H. de Paula Figueiredo
Independent Java, Apache Tapestry 5 and Hibernate consultant, developer,  
and instructor
Owner, Ars Machina Tecnologia da Informação Ltda.
http://www.arsmachina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Chuck Kring <cj...@pacbell.net>.
I didn't file a Jira because I initially ran into this with our polled 
<img> components which are somewhat out of the box..  I'll create a 
small test case using zones and file a Jira if that fails.

Chuck

Josh Canfield wrote:
>> We've had a series of problems relating to persistence of component fields
>> when components are reused in the page and called as part of an Ajax
>> request.
>>     
>
> Is this problem in jira? I did a quick search but didn't see it.
>
> On Thu, Jul 15, 2010 at 12:28 PM, Chuck Kring <cj...@pacbell.net> wrote:
>   
>> Howard,
>>
>> I have another use case where this might be very useful.
>>
>> Our application consists of several dashboard pages that are used like a
>> network management console works:  the pages automatically refresh as things
>> change, users can click on icons to drill down, etc.   The page relies
>> heavily on Ajax calls to update icons and charts and we very often use the
>> same component multiple times in a page.  This is a medical application and
>> we have icons to represent every patient state - that component is reused
>> once for every patient displayed.
>>
>> We tend to have two types of Ajax calls:  zones and images.  Images are
>> handled similar to zones but use our own Javascript that sets the img src
>> field once the image has been loaded into the browser cache.
>> We've had a series of problems relating to persistence of component fields
>> when components are reused in the page and called as part of an Ajax
>> request.   We fixed this by having components register a context with the
>> calling page, then we set the component context prior to generating the JSON
>> response.  I'm sure there is a better way to do this (perhaps a Tapestry
>> Service) but this is workable for now.  Once again, the specific issue is
>> that when multiple copies of a component are used on a page and the
>> components are returned as a response to an XHR request, persisted fields
>> are not handled correctly across all similar components.
>>
>> I think another benefit of your proposal is it could make state management
>> in components more easily controlled by the application.  For example, I
>> could forgo the above context mechanism if it were possible to retrieve a
>> handle to the FieldValueCondiut and to set that before re-rendering the
>> component.
>>
>> Regards,
>>
>> Chuck Kring
>>
>> Howard wrote:
>>     
>>> Tapestry applications are inherently stateful: during and between
>>> requests, information in Tapestry components, value stored in fields,
>>> stick around. This is a great thing: it lets you program a web
>>> application in a sensible way, using stateful objects full of mutable
>>> properties and methods to operate on those properties.
>>> It also has its downside: Tapestry has to maintain a pool of page
>>> instances. And in Tapestry, page instances are big: a tree of hundreds
>>> or perhaps thousands of interrelated objects: the tree of Tapestry
>>> structural objects that forms the basic page structure, the component
>>> and mixin objects hanging off that tree, the binding objects that
>>> connect parameters of components to properties of their containing
>>> component, the template objects that represents elements and content
>>> from component templates, and many, many more that most Tapestry
>>> developers are kept unawares of.
>>> This has proven to be a problem with biggest and busiest sites
>>> constructed using Tapestry. Keeping a pool of those objects, checking
>>> them in and out, and discarded them when no longer needed is draining
>>> needed resources, especially heap space.
>>> So that seems like an irreconcilable problem eh? Removing mutable state
>>> from pages and components would turn Tapestry into something else
>>> entirely. On the other hand, allowing mutable state means that
>>> applications, especially big complex applications with many pages,
>>> become memory hogs.
>>> I suppose one approach would be to simply create a page instance for
>>> the duration of a request, and discard it at the end. However, page
>>> construction in Tapestry is very complicated and although some effort
>>> was expended in Tapestry 5.1 to reduce the cost of page construction,
>>> it is still present. Additionally, Tapestry is full of small
>>> optimizations that improve performance ... assuming a page is reused
>>> over time. Throwing away pages is a non-starter.
>>> So we're back to square one ... we can't eliminate mutable state, but
>>> (for large applications) we can't live with it either.
>>> Tapestry has already been down this route: the way persistent fields
>>> are handled gives the illusion that the page is kept around between
>>> requests. You might think that Tapestry serializes the page and stores
>>> the whole thing in the session. In reality, Tapestry is shuffling just
>>> the individual persistent field values in to and out of the HttpSessio.
>>> To both the end user and the Tapestry developer, it feels like the
>>> entire page is live between requests, but it's a bit of a shell game,
>>> providing an equivalent page instance that has the same values in its
>>> fields.
>>> What's going on in trunk right now is extrapolating that concept from
>>> persistent fields to all mutable fields. Every access to every mutable
>>> field in a Tapestry page is converted, as part of the class
>>> transformation process, into an access against a per-thread Map of keys
>>> and values. The end result is that a single page instance can be used
>>> across threads without any synchronization issues and without any
>>> conflicts. Each thread has its own per-thread Map.
>>> This idea was suggested in years past, but the APIs to accomplish it
>>> (as well as the necessary meta-programming savvy) just wasn't
>>> available. However, as a side effect of rewriting and simplifying the
>>> class transformation APIs in 5.2, it became very reasonable to do this.
>>> Let's take an important example: handling typical, mutable fields. This
>>> is the responsibility of the UnclaimedFieldWorker class, part of
>>> Tapestry component class transformation pipeline. UnclaimedFieldWorker
>>> finds fields that have not be "claimed" by some other part of the
>>> pipeline and converts them to read and write their values to the
>>> per-thread Map. A claimed field may store an injected service, asset or
>>> component, or be a component parameter.
>>> public class UnclaimedFieldWorker implements
>>> ComponentClassTransformWorker { private final PerthreadManager
>>> perThreadManager; private final ComponentClassCache classCache; static
>>> class UnclaimedFieldConduit implements FieldValueConduit { private
>>> final InternalComponentResources resources; private final
>>> PerThreadValue<Object> fieldValue; // Set prior to the
>>> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
>>> private UnclaimedFieldConduit(InternalComponentResources resources,
>>> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
>>> this.resources = resources; this.fieldValue = fieldValue;
>>> this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
>>> return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
>>> public void set(Object newValue) { fieldValue.set(newValue); // This
>>> catches the case where the instance initializer method sets a value for
>>> the field. // That value is captured and used when no specific value
>>> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
>>> newValue; } } public UnclaimedFieldWorker(ComponentClassCache
>>> classCache, PerthreadManager perThreadManager) { this.classCache =
>>> classCache; this.perThreadManager = perThreadManager; } public void
>>> transform(ClassTransformation transformation, MutableComponentModel
>>> model) { for (TransformField field :
>>> transformation.matchUnclaimedFields()) { transformField(field); } }
>>> private void transformField(TransformField field) { int modifiers =
>>> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
>>> Modifier.isStatic(modifiers)) return;
>>> ComponentValueProvider<FieldValueConduit> provider =
>>> createFieldValueConduitProvider(field);
>>> field.replaceAccess(provider); } private
>>> ComponentValueProvider<FieldValueConduit>
>>> createFieldValueConduitProvider(TransformField field) { final String
>>> fieldName = field.getName(); final String fieldType = field.getType();
>>> return new ComponentValueProvider<FieldValueConduit>() { public
>>> FieldValueConduit get(ComponentResources resources) { Object
>>> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
>>> key = String.format("UnclaimedFieldWorker:%s/%s",
>>> resources.getCompleteId(), fieldName); return new
>>> UnclaimedFieldConduit((InternalComponentResources) resources,
>>> perThreadManager.createValue(key), fieldDefaultValue); } }; } }
>>>
>>> That seems like a lot, but lets break it down bit by bit.
>>> public void transform(ClassTransformation transformation,
>>> MutableComponentModel model) { for (TransformField field :
>>> transformation.matchUnclaimedFields()) { transformField(field); } }
>>> private void transformField(TransformField field) { int modifiers =
>>> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
>>> Modifier.isStatic(modifiers)) return;
>>> ComponentValueProvider<FieldValueConduit> provider =
>>> createFieldValueConduitProvider(field); field.replaceAccess(provider); }
>>> The transform() method is the lone method for this class, as defined by
>>> ComponentClassTransformWorker. It uses a method on the
>>> ClassTransformation to locate all the unclaimed fields. TransformField
>>> is the representation of a field of a component class during the
>>> transformation process. As we'll see it is very easy to intercept
>>> access to the field.
>>> Some of those fields are final or static and are just ignored. A
>>> ComponentValueProvider is a callback object: when the component
>>> (whatever it is) is first instantiated, the provider will be invoked
>>> and the return value stored into a new field. A FieldValueConduit is an
>>> object that takes over responsibility for access to a TransformField:
>>> internally, all read and write access to the field is passed through
>>> the conduit object.
>>> So, what we're saying is: when the component is first created, use the
>>> callback to create a conduit, and change any read or write access to
>>> the field to pass through the created conduit. If a component is
>>> instantiated multiple times (either in different pages, or within the
>>> same page) each instance of the component will end up with a specific
>>> FieldValueConduit.
>>> Fine so far; it comes down to what's inside the
>>> createFieldValueConduitProvider() method:
>>> private ComponentValueProvider<FieldValueConduit>
>>> createFieldValueConduitProvider(TransformField field) { final String
>>> fieldName = field.getName(); final String fieldType = field.getType();
>>> return new ComponentValueProvider<FieldValueConduit>() { public
>>> FieldValueConduit get(ComponentResources resources) { Object
>>> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
>>> key = String.format("UnclaimedFieldWorker:%s/%s",
>>> resources.getCompleteId(), fieldName); return new
>>> UnclaimedFieldConduit((InternalComponentResources) resources,
>>> perThreadManager.createValue(key), fieldDefaultValue); } }; }
>>>
>>> Here we capture the name of the field and its type (expressed as
>>> String). Inside the get() method we determine the initial default value
>>> for the field: typically just null, but may be 0 (for a primitive
>>> numeric field) or false (for a primitive boolean field).
>>> Next we build a unique key used to store and retrieve the field's value
>>> inside the per-thread Map. The key includes the complete id of the
>>> component and the name of the field: thus two different component
>>> instances, in the same page or across different pages, will have their
>>> own unique key.
>>> We use the PerthreadManager service to create a PerThreadValue for the
>>> field.
>>> Lastly, we create the conduit object. Let's look at the conduit in more
>>> detail:
>>> static class UnclaimedFieldConduit implements FieldValueConduit {
>>> private final InternalComponentResources resources; private final
>>> PerThreadValue<Object> fieldValue; // Set prior to the
>>> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
>>> private UnclaimedFieldConduit(InternalComponentResources resources,
>>> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
>>> this.resources = resources; this.fieldValue = fieldValue;
>>> this.fieldDefaultValue = fieldDefaultValue; }
>>>
>>> We use the special InternalComponentResources interface because we'll
>>> need to know if the page is loading, or in normal operation (that's
>>> coming up). We capture our initial guess at a default value for the
>>> field (remember: null, false or 0) but that may change.
>>> public Object get() { return fieldValue.exists() ? fieldValue.get() :
>>> fieldDefaultValue; }
>>>
>>> Whenever code inside the component reads the field, this method will be
>>> invoked. It checks to see if a value has been stored into the
>>> PerThreadValue object this request; if so the stored value is returned,
>>> otherwise the field default value is returned.
>>> Notice the distinction here between null and no value at all. Just
>>> because the field is set to null doesn't mean we should switch over the
>>> the default value (assuming the default is not null).
>>> The last hurdle is updates to the field:
>>> public void set(Object newValue) { fieldValue.set(newValue); // This
>>> catches the case where the instance initializer method sets a value for
>>> the field. // That value is captured and used when no specific value
>>> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
>>> newValue; }
>>>
>>> The basic logic is just to stuff the new value into the PerThreadValue.
>>> However, there's one special case: a field initialization (whether it's
>>> in the component's constructor, or where the field is defined) turns
>>> into a call to set(). We can differentiate because that update occurs
>>> before the page is marked as fully loaded, rather than in normal use of
>>> the page.
>>> And that's it! Now, to be honest, this is more detail than a typical
>>> Tapestry developer ever needs to know. However, it's a good
>>> demonstration of how Tapestry's class transformation APIs make Java
>>> code fluid; capable of being changed dynamically (under carefully
>>> controlled circumstances).
>>> Back to pooling: how is this going to affect performance? That's an
>>> open question, and putting together a performance testing environment
>>> is another task at the top of my list. My suspicion is that the new
>>> overhead will not make a visible difference for small applications
>>> (dozens of pages, reasonable number of concurrent users) ... but for
>>> high end sites (hundreds of pages, large numbre of concurrent users)
>>> the avoidance of pooling and page construction will make a big
>>> difference!
>>>
>>> --
>>> Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM
>>>
>>>       
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> For additional commands, e-mail: users-help@tapestry.apache.org
>>
>>
>>     
>
>
>
>   

Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Josh Canfield <jo...@gmail.com>.
> We've had a series of problems relating to persistence of component fields
> when components are reused in the page and called as part of an Ajax
> request.

Is this problem in jira? I did a quick search but didn't see it.

On Thu, Jul 15, 2010 at 12:28 PM, Chuck Kring <cj...@pacbell.net> wrote:
> Howard,
>
> I have another use case where this might be very useful.
>
> Our application consists of several dashboard pages that are used like a
> network management console works:  the pages automatically refresh as things
> change, users can click on icons to drill down, etc.   The page relies
> heavily on Ajax calls to update icons and charts and we very often use the
> same component multiple times in a page.  This is a medical application and
> we have icons to represent every patient state - that component is reused
> once for every patient displayed.
>
> We tend to have two types of Ajax calls:  zones and images.  Images are
> handled similar to zones but use our own Javascript that sets the img src
> field once the image has been loaded into the browser cache.
> We've had a series of problems relating to persistence of component fields
> when components are reused in the page and called as part of an Ajax
> request.   We fixed this by having components register a context with the
> calling page, then we set the component context prior to generating the JSON
> response.  I'm sure there is a better way to do this (perhaps a Tapestry
> Service) but this is workable for now.  Once again, the specific issue is
> that when multiple copies of a component are used on a page and the
> components are returned as a response to an XHR request, persisted fields
> are not handled correctly across all similar components.
>
> I think another benefit of your proposal is it could make state management
> in components more easily controlled by the application.  For example, I
> could forgo the above context mechanism if it were possible to retrieve a
> handle to the FieldValueCondiut and to set that before re-rendering the
> component.
>
> Regards,
>
> Chuck Kring
>
> Howard wrote:
>>
>> Tapestry applications are inherently stateful: during and between
>> requests, information in Tapestry components, value stored in fields,
>> stick around. This is a great thing: it lets you program a web
>> application in a sensible way, using stateful objects full of mutable
>> properties and methods to operate on those properties.
>> It also has its downside: Tapestry has to maintain a pool of page
>> instances. And in Tapestry, page instances are big: a tree of hundreds
>> or perhaps thousands of interrelated objects: the tree of Tapestry
>> structural objects that forms the basic page structure, the component
>> and mixin objects hanging off that tree, the binding objects that
>> connect parameters of components to properties of their containing
>> component, the template objects that represents elements and content
>> from component templates, and many, many more that most Tapestry
>> developers are kept unawares of.
>> This has proven to be a problem with biggest and busiest sites
>> constructed using Tapestry. Keeping a pool of those objects, checking
>> them in and out, and discarded them when no longer needed is draining
>> needed resources, especially heap space.
>> So that seems like an irreconcilable problem eh? Removing mutable state
>> from pages and components would turn Tapestry into something else
>> entirely. On the other hand, allowing mutable state means that
>> applications, especially big complex applications with many pages,
>> become memory hogs.
>> I suppose one approach would be to simply create a page instance for
>> the duration of a request, and discard it at the end. However, page
>> construction in Tapestry is very complicated and although some effort
>> was expended in Tapestry 5.1 to reduce the cost of page construction,
>> it is still present. Additionally, Tapestry is full of small
>> optimizations that improve performance ... assuming a page is reused
>> over time. Throwing away pages is a non-starter.
>> So we're back to square one ... we can't eliminate mutable state, but
>> (for large applications) we can't live with it either.
>> Tapestry has already been down this route: the way persistent fields
>> are handled gives the illusion that the page is kept around between
>> requests. You might think that Tapestry serializes the page and stores
>> the whole thing in the session. In reality, Tapestry is shuffling just
>> the individual persistent field values in to and out of the HttpSessio.
>> To both the end user and the Tapestry developer, it feels like the
>> entire page is live between requests, but it's a bit of a shell game,
>> providing an equivalent page instance that has the same values in its
>> fields.
>> What's going on in trunk right now is extrapolating that concept from
>> persistent fields to all mutable fields. Every access to every mutable
>> field in a Tapestry page is converted, as part of the class
>> transformation process, into an access against a per-thread Map of keys
>> and values. The end result is that a single page instance can be used
>> across threads without any synchronization issues and without any
>> conflicts. Each thread has its own per-thread Map.
>> This idea was suggested in years past, but the APIs to accomplish it
>> (as well as the necessary meta-programming savvy) just wasn't
>> available. However, as a side effect of rewriting and simplifying the
>> class transformation APIs in 5.2, it became very reasonable to do this.
>> Let's take an important example: handling typical, mutable fields. This
>> is the responsibility of the UnclaimedFieldWorker class, part of
>> Tapestry component class transformation pipeline. UnclaimedFieldWorker
>> finds fields that have not be "claimed" by some other part of the
>> pipeline and converts them to read and write their values to the
>> per-thread Map. A claimed field may store an injected service, asset or
>> component, or be a component parameter.
>> public class UnclaimedFieldWorker implements
>> ComponentClassTransformWorker { private final PerthreadManager
>> perThreadManager; private final ComponentClassCache classCache; static
>> class UnclaimedFieldConduit implements FieldValueConduit { private
>> final InternalComponentResources resources; private final
>> PerThreadValue<Object> fieldValue; // Set prior to the
>> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
>> private UnclaimedFieldConduit(InternalComponentResources resources,
>> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
>> this.resources = resources; this.fieldValue = fieldValue;
>> this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
>> return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
>> public void set(Object newValue) { fieldValue.set(newValue); // This
>> catches the case where the instance initializer method sets a value for
>> the field. // That value is captured and used when no specific value
>> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
>> newValue; } } public UnclaimedFieldWorker(ComponentClassCache
>> classCache, PerthreadManager perThreadManager) { this.classCache =
>> classCache; this.perThreadManager = perThreadManager; } public void
>> transform(ClassTransformation transformation, MutableComponentModel
>> model) { for (TransformField field :
>> transformation.matchUnclaimedFields()) { transformField(field); } }
>> private void transformField(TransformField field) { int modifiers =
>> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
>> Modifier.isStatic(modifiers)) return;
>> ComponentValueProvider<FieldValueConduit> provider =
>> createFieldValueConduitProvider(field);
>> field.replaceAccess(provider); } private
>> ComponentValueProvider<FieldValueConduit>
>> createFieldValueConduitProvider(TransformField field) { final String
>> fieldName = field.getName(); final String fieldType = field.getType();
>> return new ComponentValueProvider<FieldValueConduit>() { public
>> FieldValueConduit get(ComponentResources resources) { Object
>> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
>> key = String.format("UnclaimedFieldWorker:%s/%s",
>> resources.getCompleteId(), fieldName); return new
>> UnclaimedFieldConduit((InternalComponentResources) resources,
>> perThreadManager.createValue(key), fieldDefaultValue); } }; } }
>>
>> That seems like a lot, but lets break it down bit by bit.
>> public void transform(ClassTransformation transformation,
>> MutableComponentModel model) { for (TransformField field :
>> transformation.matchUnclaimedFields()) { transformField(field); } }
>> private void transformField(TransformField field) { int modifiers =
>> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
>> Modifier.isStatic(modifiers)) return;
>> ComponentValueProvider<FieldValueConduit> provider =
>> createFieldValueConduitProvider(field); field.replaceAccess(provider); }
>> The transform() method is the lone method for this class, as defined by
>> ComponentClassTransformWorker. It uses a method on the
>> ClassTransformation to locate all the unclaimed fields. TransformField
>> is the representation of a field of a component class during the
>> transformation process. As we'll see it is very easy to intercept
>> access to the field.
>> Some of those fields are final or static and are just ignored. A
>> ComponentValueProvider is a callback object: when the component
>> (whatever it is) is first instantiated, the provider will be invoked
>> and the return value stored into a new field. A FieldValueConduit is an
>> object that takes over responsibility for access to a TransformField:
>> internally, all read and write access to the field is passed through
>> the conduit object.
>> So, what we're saying is: when the component is first created, use the
>> callback to create a conduit, and change any read or write access to
>> the field to pass through the created conduit. If a component is
>> instantiated multiple times (either in different pages, or within the
>> same page) each instance of the component will end up with a specific
>> FieldValueConduit.
>> Fine so far; it comes down to what's inside the
>> createFieldValueConduitProvider() method:
>> private ComponentValueProvider<FieldValueConduit>
>> createFieldValueConduitProvider(TransformField field) { final String
>> fieldName = field.getName(); final String fieldType = field.getType();
>> return new ComponentValueProvider<FieldValueConduit>() { public
>> FieldValueConduit get(ComponentResources resources) { Object
>> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
>> key = String.format("UnclaimedFieldWorker:%s/%s",
>> resources.getCompleteId(), fieldName); return new
>> UnclaimedFieldConduit((InternalComponentResources) resources,
>> perThreadManager.createValue(key), fieldDefaultValue); } }; }
>>
>> Here we capture the name of the field and its type (expressed as
>> String). Inside the get() method we determine the initial default value
>> for the field: typically just null, but may be 0 (for a primitive
>> numeric field) or false (for a primitive boolean field).
>> Next we build a unique key used to store and retrieve the field's value
>> inside the per-thread Map. The key includes the complete id of the
>> component and the name of the field: thus two different component
>> instances, in the same page or across different pages, will have their
>> own unique key.
>> We use the PerthreadManager service to create a PerThreadValue for the
>> field.
>> Lastly, we create the conduit object. Let's look at the conduit in more
>> detail:
>> static class UnclaimedFieldConduit implements FieldValueConduit {
>> private final InternalComponentResources resources; private final
>> PerThreadValue<Object> fieldValue; // Set prior to the
>> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
>> private UnclaimedFieldConduit(InternalComponentResources resources,
>> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
>> this.resources = resources; this.fieldValue = fieldValue;
>> this.fieldDefaultValue = fieldDefaultValue; }
>>
>> We use the special InternalComponentResources interface because we'll
>> need to know if the page is loading, or in normal operation (that's
>> coming up). We capture our initial guess at a default value for the
>> field (remember: null, false or 0) but that may change.
>> public Object get() { return fieldValue.exists() ? fieldValue.get() :
>> fieldDefaultValue; }
>>
>> Whenever code inside the component reads the field, this method will be
>> invoked. It checks to see if a value has been stored into the
>> PerThreadValue object this request; if so the stored value is returned,
>> otherwise the field default value is returned.
>> Notice the distinction here between null and no value at all. Just
>> because the field is set to null doesn't mean we should switch over the
>> the default value (assuming the default is not null).
>> The last hurdle is updates to the field:
>> public void set(Object newValue) { fieldValue.set(newValue); // This
>> catches the case where the instance initializer method sets a value for
>> the field. // That value is captured and used when no specific value
>> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
>> newValue; }
>>
>> The basic logic is just to stuff the new value into the PerThreadValue.
>> However, there's one special case: a field initialization (whether it's
>> in the component's constructor, or where the field is defined) turns
>> into a call to set(). We can differentiate because that update occurs
>> before the page is marked as fully loaded, rather than in normal use of
>> the page.
>> And that's it! Now, to be honest, this is more detail than a typical
>> Tapestry developer ever needs to know. However, it's a good
>> demonstration of how Tapestry's class transformation APIs make Java
>> code fluid; capable of being changed dynamically (under carefully
>> controlled circumstances).
>> Back to pooling: how is this going to affect performance? That's an
>> open question, and putting together a performance testing environment
>> is another task at the top of my list. My suspicion is that the new
>> overhead will not make a visible difference for small applications
>> (dozens of pages, reasonable number of concurrent users) ... but for
>> high end sites (hundreds of pages, large numbre of concurrent users)
>> the avoidance of pooling and page construction will make a big
>> difference!
>>
>> --
>> Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>



-- 
--
http://www.bodylabgym.com - a private, by appointment only, one-on-one
health and fitness facility.
--
http://www.ectransition.com - Quality Electronic Cigarettes at a
reasonable price!
--
TheDailyTube.com. Sign up and get the best new videos on the internet
delivered fresh to your inbox.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Chuck Kring <cj...@pacbell.net>.
Howard,

I have another use case where this might be very useful.

Our application consists of several dashboard pages that are used like a 
network management console works:  the pages automatically refresh as 
things change, users can click on icons to drill down, etc.   The page 
relies heavily on Ajax calls to update icons and charts and we very 
often use the same component multiple times in a page.  This is a 
medical application and we have icons to represent every patient state - 
that component is reused once for every patient displayed.

We tend to have two types of Ajax calls:  zones and images.  Images are 
handled similar to zones but use our own Javascript that sets the img 
src field once the image has been loaded into the browser cache.  

We've had a series of problems relating to persistence of component 
fields when components are reused in the page and called as part of an 
Ajax request.   We fixed this by having components register a context 
with the calling page, then we set the component context prior to 
generating the JSON response.  I'm sure there is a better way to do this 
(perhaps a Tapestry Service) but this is workable for now.  Once again, 
the specific issue is that when multiple copies of a component are used 
on a page and the components are returned as a response to an XHR 
request, persisted fields are not handled correctly across all similar 
components.

I think another benefit of your proposal is it could make state 
management in components more easily controlled by the application.  For 
example, I could forgo the above context mechanism if it were possible 
to retrieve a handle to the FieldValueCondiut and to set that before 
re-rendering the component.

Regards,

Chuck Kring

Howard wrote:
> Tapestry applications are inherently stateful: during and between
> requests, information in Tapestry components, value stored in fields,
> stick around. This is a great thing: it lets you program a web
> application in a sensible way, using stateful objects full of mutable
> properties and methods to operate on those properties.
> It also has its downside: Tapestry has to maintain a pool of page
> instances. And in Tapestry, page instances are big: a tree of hundreds
> or perhaps thousands of interrelated objects: the tree of Tapestry
> structural objects that forms the basic page structure, the component
> and mixin objects hanging off that tree, the binding objects that
> connect parameters of components to properties of their containing
> component, the template objects that represents elements and content
> from component templates, and many, many more that most Tapestry
> developers are kept unawares of.
> This has proven to be a problem with biggest and busiest sites
> constructed using Tapestry. Keeping a pool of those objects, checking
> them in and out, and discarded them when no longer needed is draining
> needed resources, especially heap space.
> So that seems like an irreconcilable problem eh? Removing mutable state
> from pages and components would turn Tapestry into something else
> entirely. On the other hand, allowing mutable state means that
> applications, especially big complex applications with many pages,
> become memory hogs.
> I suppose one approach would be to simply create a page instance for
> the duration of a request, and discard it at the end. However, page
> construction in Tapestry is very complicated and although some effort
> was expended in Tapestry 5.1 to reduce the cost of page construction,
> it is still present. Additionally, Tapestry is full of small
> optimizations that improve performance ... assuming a page is reused
> over time. Throwing away pages is a non-starter.
> So we're back to square one ... we can't eliminate mutable state, but
> (for large applications) we can't live with it either.
> Tapestry has already been down this route: the way persistent fields
> are handled gives the illusion that the page is kept around between
> requests. You might think that Tapestry serializes the page and stores
> the whole thing in the session. In reality, Tapestry is shuffling just
> the individual persistent field values in to and out of the HttpSessio.
> To both the end user and the Tapestry developer, it feels like the
> entire page is live between requests, but it's a bit of a shell game,
> providing an equivalent page instance that has the same values in its
> fields.
> What's going on in trunk right now is extrapolating that concept from
> persistent fields to all mutable fields. Every access to every mutable
> field in a Tapestry page is converted, as part of the class
> transformation process, into an access against a per-thread Map of keys
> and values. The end result is that a single page instance can be used
> across threads without any synchronization issues and without any
> conflicts. Each thread has its own per-thread Map.
> This idea was suggested in years past, but the APIs to accomplish it
> (as well as the necessary meta-programming savvy) just wasn't
> available. However, as a side effect of rewriting and simplifying the
> class transformation APIs in 5.2, it became very reasonable to do this.
> Let's take an important example: handling typical, mutable fields. This
> is the responsibility of the UnclaimedFieldWorker class, part of
> Tapestry component class transformation pipeline. UnclaimedFieldWorker
> finds fields that have not be "claimed" by some other part of the
> pipeline and converts them to read and write their values to the
> per-thread Map. A claimed field may store an injected service, asset or
> component, or be a component parameter.
> public class UnclaimedFieldWorker implements
> ComponentClassTransformWorker { private final PerthreadManager
> perThreadManager; private final ComponentClassCache classCache; static
> class UnclaimedFieldConduit implements FieldValueConduit { private
> final InternalComponentResources resources; private final
> PerThreadValue<Object> fieldValue; // Set prior to the
> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
> private UnclaimedFieldConduit(InternalComponentResources resources,
> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
> this.resources = resources; this.fieldValue = fieldValue;
> this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
> return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
> public void set(Object newValue) { fieldValue.set(newValue); // This
> catches the case where the instance initializer method sets a value for
> the field. // That value is captured and used when no specific value
> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
> newValue; } } public UnclaimedFieldWorker(ComponentClassCache
> classCache, PerthreadManager perThreadManager) { this.classCache =
> classCache; this.perThreadManager = perThreadManager; } public void
> transform(ClassTransformation transformation, MutableComponentModel
> model) { for (TransformField field :
> transformation.matchUnclaimedFields()) { transformField(field); } }
> private void transformField(TransformField field) { int modifiers =
> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
> Modifier.isStatic(modifiers)) return;
> ComponentValueProvider<FieldValueConduit> provider =
> createFieldValueConduitProvider(field);
> field.replaceAccess(provider); } private
> ComponentValueProvider<FieldValueConduit>
> createFieldValueConduitProvider(TransformField field) { final String
> fieldName = field.getName(); final String fieldType = field.getType();
> return new ComponentValueProvider<FieldValueConduit>() { public
> FieldValueConduit get(ComponentResources resources) { Object
> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
> key = String.format("UnclaimedFieldWorker:%s/%s",
> resources.getCompleteId(), fieldName); return new
> UnclaimedFieldConduit((InternalComponentResources) resources,
> perThreadManager.createValue(key), fieldDefaultValue); } }; } }
>
> That seems like a lot, but lets break it down bit by bit.
> public void transform(ClassTransformation transformation,
> MutableComponentModel model) { for (TransformField field :
> transformation.matchUnclaimedFields()) { transformField(field); } }
> private void transformField(TransformField field) { int modifiers =
> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
> Modifier.isStatic(modifiers)) return;
> ComponentValueProvider<FieldValueConduit> provider =
> createFieldValueConduitProvider(field); field.replaceAccess(provider); }
> The transform() method is the lone method for this class, as defined by
> ComponentClassTransformWorker. It uses a method on the
> ClassTransformation to locate all the unclaimed fields. TransformField
> is the representation of a field of a component class during the
> transformation process. As we'll see it is very easy to intercept
> access to the field.
> Some of those fields are final or static and are just ignored. A
> ComponentValueProvider is a callback object: when the component
> (whatever it is) is first instantiated, the provider will be invoked
> and the return value stored into a new field. A FieldValueConduit is an
> object that takes over responsibility for access to a TransformField:
> internally, all read and write access to the field is passed through
> the conduit object.
> So, what we're saying is: when the component is first created, use the
> callback to create a conduit, and change any read or write access to
> the field to pass through the created conduit. If a component is
> instantiated multiple times (either in different pages, or within the
> same page) each instance of the component will end up with a specific
> FieldValueConduit.
> Fine so far; it comes down to what's inside the
> createFieldValueConduitProvider() method:
> private ComponentValueProvider<FieldValueConduit>
> createFieldValueConduitProvider(TransformField field) { final String
> fieldName = field.getName(); final String fieldType = field.getType();
> return new ComponentValueProvider<FieldValueConduit>() { public
> FieldValueConduit get(ComponentResources resources) { Object
> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
> key = String.format("UnclaimedFieldWorker:%s/%s",
> resources.getCompleteId(), fieldName); return new
> UnclaimedFieldConduit((InternalComponentResources) resources,
> perThreadManager.createValue(key), fieldDefaultValue); } }; }
>
> Here we capture the name of the field and its type (expressed as
> String). Inside the get() method we determine the initial default value
> for the field: typically just null, but may be 0 (for a primitive
> numeric field) or false (for a primitive boolean field).
> Next we build a unique key used to store and retrieve the field's value
> inside the per-thread Map. The key includes the complete id of the
> component and the name of the field: thus two different component
> instances, in the same page or across different pages, will have their
> own unique key.
> We use the PerthreadManager service to create a PerThreadValue for the
> field.
> Lastly, we create the conduit object. Let's look at the conduit in more
> detail:
> static class UnclaimedFieldConduit implements FieldValueConduit {
> private final InternalComponentResources resources; private final
> PerThreadValue<Object> fieldValue; // Set prior to the
> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
> private UnclaimedFieldConduit(InternalComponentResources resources,
> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
> this.resources = resources; this.fieldValue = fieldValue;
> this.fieldDefaultValue = fieldDefaultValue; }
>
> We use the special InternalComponentResources interface because we'll
> need to know if the page is loading, or in normal operation (that's
> coming up). We capture our initial guess at a default value for the
> field (remember: null, false or 0) but that may change.
> public Object get() { return fieldValue.exists() ? fieldValue.get() :
> fieldDefaultValue; }
>
> Whenever code inside the component reads the field, this method will be
> invoked. It checks to see if a value has been stored into the
> PerThreadValue object this request; if so the stored value is returned,
> otherwise the field default value is returned.
> Notice the distinction here between null and no value at all. Just
> because the field is set to null doesn't mean we should switch over the
> the default value (assuming the default is not null).
> The last hurdle is updates to the field:
> public void set(Object newValue) { fieldValue.set(newValue); // This
> catches the case where the instance initializer method sets a value for
> the field. // That value is captured and used when no specific value
> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
> newValue; }
>
> The basic logic is just to stuff the new value into the PerThreadValue.
> However, there's one special case: a field initialization (whether it's
> in the component's constructor, or where the field is defined) turns
> into a call to set(). We can differentiate because that update occurs
> before the page is marked as fully loaded, rather than in normal use of
> the page.
> And that's it! Now, to be honest, this is more detail than a typical
> Tapestry developer ever needs to know. However, it's a good
> demonstration of how Tapestry's class transformation APIs make Java
> code fluid; capable of being changed dynamically (under carefully
> controlled circumstances).
> Back to pooling: how is this going to affect performance? That's an
> open question, and putting together a performance testing environment
> is another task at the top of my list. My suspicion is that the new
> overhead will not make a visible difference for small applications
> (dozens of pages, reasonable number of concurrent users) ... but for
> high end sites (hundreds of pages, large numbre of concurrent users)
> the avoidance of pooling and page construction will make a big
> difference!
>
> --
> Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM
>   

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by "Thiago H. de Paula Figueiredo" <th...@gmail.com>.
On Thu, 15 Jul 2010 14:16:13 -0300, Pierce Wetter <pi...@paceap.com>  
wrote:

>   Ok, this is all in the name of performance memory and otherwise,

There was a serious memory usage in applications with lots of pages and  
many concurrent users.

>   You're completely changing the architecture to something you are  
> assuming will be faster,

This change wasn't aimed at speed.

> but this new architecture seems like it will need lots of small tweaks.

I don't know if it will need, but tweaks can and will be made.

By the way, thanks for your insights.

>  So let me also propose the counter argument, which if I understand  
> correctly, that persistent fields already work that way, so really this  
> is extending regular fields to work the same way as persistent fields.  
> If that's true, then I'm only half right:

It's true. This change is way smaller than you're probably thinking.  
Tapestry's code is almost as uncoupled and flexible as it's possible.

>      2. I was right: We should come up with some performance tests for  
> this. That might just be a matter of asking the guy who had been doing  
> Tapestry capacity testing to run his tests against trunk.

Howard said that in his blog post.

-- 
Thiago H. de Paula Figueiredo
Independent Java, Apache Tapestry 5 and Hibernate consultant, developer,  
and instructor
Owner, Ars Machina Tecnologia da Informação Ltda.
http://www.arsmachina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Pierce Wetter <pi...@paceap.com>.
On Jul 15, 2010, at 9:41 AM, Howard Lewis Ship wrote:

> As soon as we have the new Confluence documentation system up and
> running, I'll be pushing to get an alpha out, switch to beta
> development (bug fixing) and documentation.  I think it'll be a short
> beta ... but we do have an awful lot of little bugs that could use
> some TLC.

  Yeah, so it seems to me like 5.2 has some good stuff in it that should get pushed out, like running easily under GlassFish 3.x, etc. But now you want to rip up the floorboards to implement a performance optimization. But ripping up the floorboards is making me nervous. 

  Specifically, the thing that bothers me about this is your last paragraph:

>    Back to pooling: how is this going to affect performance? That's an open question, and putting together a performance testing environment is another task at the top of my list. My suspicion is that the new overhead will not make a visible difference for small applications (dozens of pages, reasonable number of concurrent users) ... but for high end sites (hundreds of pages, large numbers of concurrent users) the avoidance of pooling and page construction will make a big difference!

  Ok, this is all in the name of performance memory and otherwise, but the code is being developed before the test structure is implemented? Premature Optimization, its worse than Premature Ejaculation! Measure, then optimize!

  You're completely changing the architecture to something you are assuming will be faster, but this new architecture seems like it will need lots of small tweaks. For instance, if you're using a Map to store the values, it might make a lot of performance sense to pre-allocate the right capacity for the Map, because after scanning the page, you'll know how many fields you have to store. That means you need to collect meta info about each page somewhere. 

  Other little things: Your key is: String key = String.format("UnclaimedFieldWorker:%s/%s",

  That means that the beginning of the key is always the same. Even though the lookup is probably a hash, it would be better to have the most widely varying data in the front, not the back. Even better might be not to use a Map at all, but rather map field names to indexes as you go and use an Array; though that would have worse memory performance if you have 100 default values and 1 custom value. 

  All of that tells me that this is something that should be done in a branch. BUT...

  I'm sure you feel beat up right now. I don't want you to feel that way. 

 So let me also propose the counter argument, which if I understand correctly, that persistent fields already work that way, so really this is extending regular fields to work the same way as persistent fields. If that's true, then I'm only half right:

     1. I was wrong:   To quote Thiago: "From the Tapestry user point of view, it's a completely internal, transparent change."

     2. I was right: We should come up with some performance tests for this. That might just be a matter of asking the guy who had been doing Tapestry capacity testing to run his tests against trunk. 

 Pierce
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Howard Lewis Ship <hl...@gmail.com>.
As soon as we have the new Confluence documentation system up and
running, I'll be pushing to get an alpha out, switch to beta
development (bug fixing) and documentation.  I think it'll be a short
beta ... but we do have an awful lot of little bugs that could use
some TLC.

On Thu, Jul 15, 2010 at 9:03 AM, Blower, Andy
<An...@proquest.co.uk> wrote:
> Me too, like in the next week! :)
>
>> -----Original Message-----
>> From: Christian Edward Gruber [mailto:christianedwardgruber@gmail.com]
>> Sent: 15 July 2010 16:59
>> To: Tapestry users
>> Subject: Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes
>> singleton!
>>
>> 5.2 hasn't even pushed an alpha release.  It's by no means
>> inappropriate to include it in 5.2, though I'd love to see 5.2 locked
>> down some enough to push an alpha very soon.
>>
>> Christian.
>>
>> On Jul 15, 2010, at 11:48 AM, Pierce Wetter wrote:
>>
>> >
>> >
>> >  This seems like a pretty dramatic change. Should it go into a 5.3,
>> > with 5.2 frozen except for fixes and targeted for release?
>> >
>> > Pierce
>> > ---------------------------------------------------------------------
>> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> > For additional commands, e-mail: users-help@tapestry.apache.org
>> >
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> For additional commands, e-mail: users-help@tapestry.apache.org
>>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>



-- 
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


RE: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by "Blower, Andy" <An...@proquest.co.uk>.
Me too, like in the next week! :)

> -----Original Message-----
> From: Christian Edward Gruber [mailto:christianedwardgruber@gmail.com]
> Sent: 15 July 2010 16:59
> To: Tapestry users
> Subject: Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes
> singleton!
> 
> 5.2 hasn't even pushed an alpha release.  It's by no means
> inappropriate to include it in 5.2, though I'd love to see 5.2 locked
> down some enough to push an alpha very soon.
> 
> Christian.
> 
> On Jul 15, 2010, at 11:48 AM, Pierce Wetter wrote:
> 
> >
> >
> >  This seems like a pretty dramatic change. Should it go into a 5.3,
> > with 5.2 frozen except for fixes and targeted for release?
> >
> > Pierce
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > For additional commands, e-mail: users-help@tapestry.apache.org
> >
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
> 



---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Christian Edward Gruber <ch...@gmail.com>.
5.2 hasn't even pushed an alpha release.  It's by no means  
inappropriate to include it in 5.2, though I'd love to see 5.2 locked  
down some enough to push an alpha very soon.

Christian.

On Jul 15, 2010, at 11:48 AM, Pierce Wetter wrote:

>
>
>  This seems like a pretty dramatic change. Should it go into a 5.3,  
> with 5.2 frozen except for fixes and targeted for release?
>
> Pierce
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by "Thiago H. de Paula Figueiredo" <th...@gmail.com>.
On Thu, 15 Jul 2010 12:48:17 -0300, Pierce Wetter <pi...@paceap.com>  
wrote:

>   This seems like a pretty dramatic change.

 From the Tapestry user point of view, it's a completely internal,  
transparent change.

-- 
Thiago H. de Paula Figueiredo
Independent Java, Apache Tapestry 5 and Hibernate consultant, developer,  
and instructor
Owner, Ars Machina Tecnologia da Informação Ltda.
http://www.arsmachina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Pierce Wetter <pi...@paceap.com>.

  This seems like a pretty dramatic change. Should it go into a 5.3, with 5.2 frozen except for fixes and targeted for release?

 Pierce
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: [Tapestry Central] Everyone out of the Pool! Tapestry goes singleton!

Posted by Howard Lewis Ship <hl...@gmail.com>.
Read it with formatting:
http://tapestryjava.blogspot.com/2010/07/everyone-out-of-pool-tapestry-goes.html

On Wed, Jul 14, 2010 at 4:30 PM, Howard <hl...@gmail.com> wrote:
> Tapestry applications are inherently stateful: during and between
> requests, information in Tapestry components, value stored in fields,
> stick around. This is a great thing: it lets you program a web
> application in a sensible way, using stateful objects full of mutable
> properties and methods to operate on those properties.
> It also has its downside: Tapestry has to maintain a pool of page
> instances. And in Tapestry, page instances are big: a tree of hundreds
> or perhaps thousands of interrelated objects: the tree of Tapestry
> structural objects that forms the basic page structure, the component
> and mixin objects hanging off that tree, the binding objects that
> connect parameters of components to properties of their containing
> component, the template objects that represents elements and content
> from component templates, and many, many more that most Tapestry
> developers are kept unawares of.
> This has proven to be a problem with biggest and busiest sites
> constructed using Tapestry. Keeping a pool of those objects, checking
> them in and out, and discarded them when no longer needed is draining
> needed resources, especially heap space.
> So that seems like an irreconcilable problem eh? Removing mutable state
> from pages and components would turn Tapestry into something else
> entirely. On the other hand, allowing mutable state means that
> applications, especially big complex applications with many pages,
> become memory hogs.
> I suppose one approach would be to simply create a page instance for
> the duration of a request, and discard it at the end. However, page
> construction in Tapestry is very complicated and although some effort
> was expended in Tapestry 5.1 to reduce the cost of page construction,
> it is still present. Additionally, Tapestry is full of small
> optimizations that improve performance ... assuming a page is reused
> over time. Throwing away pages is a non-starter.
> So we're back to square one ... we can't eliminate mutable state, but
> (for large applications) we can't live with it either.
> Tapestry has already been down this route: the way persistent fields
> are handled gives the illusion that the page is kept around between
> requests. You might think that Tapestry serializes the page and stores
> the whole thing in the session. In reality, Tapestry is shuffling just
> the individual persistent field values in to and out of the HttpSessio.
> To both the end user and the Tapestry developer, it feels like the
> entire page is live between requests, but it's a bit of a shell game,
> providing an equivalent page instance that has the same values in its
> fields.
> What's going on in trunk right now is extrapolating that concept from
> persistent fields to all mutable fields. Every access to every mutable
> field in a Tapestry page is converted, as part of the class
> transformation process, into an access against a per-thread Map of keys
> and values. The end result is that a single page instance can be used
> across threads without any synchronization issues and without any
> conflicts. Each thread has its own per-thread Map.
> This idea was suggested in years past, but the APIs to accomplish it
> (as well as the necessary meta-programming savvy) just wasn't
> available. However, as a side effect of rewriting and simplifying the
> class transformation APIs in 5.2, it became very reasonable to do this.
> Let's take an important example: handling typical, mutable fields. This
> is the responsibility of the UnclaimedFieldWorker class, part of
> Tapestry component class transformation pipeline. UnclaimedFieldWorker
> finds fields that have not be "claimed" by some other part of the
> pipeline and converts them to read and write their values to the
> per-thread Map. A claimed field may store an injected service, asset or
> component, or be a component parameter.
> public class UnclaimedFieldWorker implements
> ComponentClassTransformWorker { private final PerthreadManager
> perThreadManager; private final ComponentClassCache classCache; static
> class UnclaimedFieldConduit implements FieldValueConduit { private
> final InternalComponentResources resources; private final
> PerThreadValue<Object> fieldValue; // Set prior to the
> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
> private UnclaimedFieldConduit(InternalComponentResources resources,
> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
> this.resources = resources; this.fieldValue = fieldValue;
> this.fieldDefaultValue = fieldDefaultValue; } public Object get() {
> return fieldValue.exists() ? fieldValue.get() : fieldDefaultValue; }
> public void set(Object newValue) { fieldValue.set(newValue); // This
> catches the case where the instance initializer method sets a value for
> the field. // That value is captured and used when no specific value
> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
> newValue; } } public UnclaimedFieldWorker(ComponentClassCache
> classCache, PerthreadManager perThreadManager) { this.classCache =
> classCache; this.perThreadManager = perThreadManager; } public void
> transform(ClassTransformation transformation, MutableComponentModel
> model) { for (TransformField field :
> transformation.matchUnclaimedFields()) { transformField(field); } }
> private void transformField(TransformField field) { int modifiers =
> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
> Modifier.isStatic(modifiers)) return;
> ComponentValueProvider<FieldValueConduit> provider =
> createFieldValueConduitProvider(field);
> field.replaceAccess(provider); } private
> ComponentValueProvider<FieldValueConduit>
> createFieldValueConduitProvider(TransformField field) { final String
> fieldName = field.getName(); final String fieldType = field.getType();
> return new ComponentValueProvider<FieldValueConduit>() { public
> FieldValueConduit get(ComponentResources resources) { Object
> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
> key = String.format("UnclaimedFieldWorker:%s/%s",
> resources.getCompleteId(), fieldName); return new
> UnclaimedFieldConduit((InternalComponentResources) resources,
> perThreadManager.createValue(key), fieldDefaultValue); } }; } }
>
> That seems like a lot, but lets break it down bit by bit.
> public void transform(ClassTransformation transformation,
> MutableComponentModel model) { for (TransformField field :
> transformation.matchUnclaimedFields()) { transformField(field); } }
> private void transformField(TransformField field) { int modifiers =
> field.getModifiers(); if (Modifier.isFinal(modifiers) ||
> Modifier.isStatic(modifiers)) return;
> ComponentValueProvider<FieldValueConduit> provider =
> createFieldValueConduitProvider(field); field.replaceAccess(provider); }
> The transform() method is the lone method for this class, as defined by
> ComponentClassTransformWorker. It uses a method on the
> ClassTransformation to locate all the unclaimed fields. TransformField
> is the representation of a field of a component class during the
> transformation process. As we'll see it is very easy to intercept
> access to the field.
> Some of those fields are final or static and are just ignored. A
> ComponentValueProvider is a callback object: when the component
> (whatever it is) is first instantiated, the provider will be invoked
> and the return value stored into a new field. A FieldValueConduit is an
> object that takes over responsibility for access to a TransformField:
> internally, all read and write access to the field is passed through
> the conduit object.
> So, what we're saying is: when the component is first created, use the
> callback to create a conduit, and change any read or write access to
> the field to pass through the created conduit. If a component is
> instantiated multiple times (either in different pages, or within the
> same page) each instance of the component will end up with a specific
> FieldValueConduit.
> Fine so far; it comes down to what's inside the
> createFieldValueConduitProvider() method:
> private ComponentValueProvider<FieldValueConduit>
> createFieldValueConduitProvider(TransformField field) { final String
> fieldName = field.getName(); final String fieldType = field.getType();
> return new ComponentValueProvider<FieldValueConduit>() { public
> FieldValueConduit get(ComponentResources resources) { Object
> fieldDefaultValue = classCache.defaultValueForType(fieldType); String
> key = String.format("UnclaimedFieldWorker:%s/%s",
> resources.getCompleteId(), fieldName); return new
> UnclaimedFieldConduit((InternalComponentResources) resources,
> perThreadManager.createValue(key), fieldDefaultValue); } }; }
>
> Here we capture the name of the field and its type (expressed as
> String). Inside the get() method we determine the initial default value
> for the field: typically just null, but may be 0 (for a primitive
> numeric field) or false (for a primitive boolean field).
> Next we build a unique key used to store and retrieve the field's value
> inside the per-thread Map. The key includes the complete id of the
> component and the name of the field: thus two different component
> instances, in the same page or across different pages, will have their
> own unique key.
> We use the PerthreadManager service to create a PerThreadValue for the
> field.
> Lastly, we create the conduit object. Let's look at the conduit in more
> detail:
> static class UnclaimedFieldConduit implements FieldValueConduit {
> private final InternalComponentResources resources; private final
> PerThreadValue<Object> fieldValue; // Set prior to the
> containingPageDidLoad lifecycle event private Object fieldDefaultValue;
> private UnclaimedFieldConduit(InternalComponentResources resources,
> PerThreadValue<Object> fieldValue, Object fieldDefaultValue) {
> this.resources = resources; this.fieldValue = fieldValue;
> this.fieldDefaultValue = fieldDefaultValue; }
>
> We use the special InternalComponentResources interface because we'll
> need to know if the page is loading, or in normal operation (that's
> coming up). We capture our initial guess at a default value for the
> field (remember: null, false or 0) but that may change.
> public Object get() { return fieldValue.exists() ? fieldValue.get() :
> fieldDefaultValue; }
>
> Whenever code inside the component reads the field, this method will be
> invoked. It checks to see if a value has been stored into the
> PerThreadValue object this request; if so the stored value is returned,
> otherwise the field default value is returned.
> Notice the distinction here between null and no value at all. Just
> because the field is set to null doesn't mean we should switch over the
> the default value (assuming the default is not null).
> The last hurdle is updates to the field:
> public void set(Object newValue) { fieldValue.set(newValue); // This
> catches the case where the instance initializer method sets a value for
> the field. // That value is captured and used when no specific value
> has been stored. if (!resources.isLoaded()) fieldDefaultValue =
> newValue; }
>
> The basic logic is just to stuff the new value into the PerThreadValue.
> However, there's one special case: a field initialization (whether it's
> in the component's constructor, or where the field is defined) turns
> into a call to set(). We can differentiate because that update occurs
> before the page is marked as fully loaded, rather than in normal use of
> the page.
> And that's it! Now, to be honest, this is more detail than a typical
> Tapestry developer ever needs to know. However, it's a good
> demonstration of how Tapestry's class transformation APIs make Java
> code fluid; capable of being changed dynamically (under carefully
> controlled circumstances).
> Back to pooling: how is this going to affect performance? That's an
> open question, and putting together a performance testing environment
> is another task at the top of my list. My suspicion is that the new
> overhead will not make a visible difference for small applications
> (dozens of pages, reasonable number of concurrent users) ... but for
> high end sites (hundreds of pages, large numbre of concurrent users)
> the avoidance of pooling and page construction will make a big
> difference!
>
> --
> Posted By Howard to Tapestry Central at 7/14/2010 04:30:00 PM



-- 
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org