On 7/05/2013 8:17 PM, Peter Firmstone wrote:
This test smells broken?
I modified the output to read in milliseconds.
Relevant ServiceDiscoveryManager method under test:
public ServiceItem[] lookup(ServiceTemplate tmpl,
int minMatches,
int maxMatches,
ServiceItemFilter filter,
long waitDur)
throws InterruptedException,
RemoteException {...}
To summarise what's happens (occassionally):
CASE 1:
* ServiceDiscoveryManager.lookup is called and blocks waiting until
it receives the minimum number of ServiceItems.
* If the required number of ServiceItem's are discovered late, the
waitDur limit will be approached when lookup returns.
o The resolution of the test is to the nearest second, not
millisecond.
Firstly I don't believe this is the intent of the Jini Standard
SD4.1.3 The blocking feature of lookup, which states:
As noted above, each category contains a version of |lookup|
that provides a feature in which the entity can request that if
the number of service references found throughout the available
lookup services does not fall into a desired range, the method
will wait a finite period of time until either an acceptable
minimum number of service references are discovered or the
specified time period has passed.
In this case the specified time period hasn't quite passed, however
an acceptable minimum number of service references are returned and
the test fails. This failure is rare.
CASE 2:
* 3. while lookup() is blocking, if enough new services are
* registered so that the acceptable minimum is achieved,
* lookup() will return immediately; that is, even if
there
* is more time left on the wait period, lookup() will not
* wait for more services beyond the minimum.
*
* For example, if 3 services are initially registered and
* lookup is called with min = 4 and max = 7, then
lookup()
* will find the 3 services and then wait for more
services to
* be registered. Suppose that while lookup() is blocking
* another 5 services are registered, bringing the
total number
* of services to 8. In this case, lookup() will stopping
* waiting and return 4 services (the minimum), not the
* maximum 7.
While the test performs this check, it isn't implemented in
ServiceDiscoveryManager, instead it's left up to chance that the
lookup method, waiting on an object monitor will obtain the monitor as
soon as it's notified. However if multiple threads contend for the
lock, it's very unlikely the thread scheduler will give the object
monitor to the waiting thread, instead the most recently blocking
thread is likely to get the monitor.
Furthermore, if this does occur, the existing ServiceDiscoveryManager
can return more ServiceItem's than maxMatches because it assumes it will
always obtain the object monitor, I've changed the implementation with
the most recent commit; additional ServiceItem's returned will be
discarded if more than maxMatches are found.
This requirement in the test appears to be additional to the
specification and would require ServiceDiscoveryManager be modified,
to use some kind of fair Lock scheme. However doing this would
unnecessarily complicate ServiceDiscoveryManager, instead it would be
simpler to return the number of ServiceItem's found up to the
maxMatches at the time the lock is obtained.
CASE 1 test results:
Running
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
Time is Mon May 06 22:17:31 EST 2013
Starting test in separate process with command:
'C:\Program Files\Java\jdk1.6.0_26\jre\bin\java'
-Djava.security.manager=org.apache.river.api.security.CombinerSecurityManager
-Djava.security.policy=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/policy/defaulttest.policy
-Djava.rmi.server.codebase=http://medusa:9082/qa1-servicediscovery-dl.jar
-cp
C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-platform.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\high-scale-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\custard-apple-1.0.3.jar
-ea -esa -client '-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
-Dcom.sun.jini.jsk.port=9080 -Dcom.sun.jini.qa.port=9081
-Dcom.sun.jini.jsk.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy
-Dcom.sun.jini.qa.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.qa.harness.harnessJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar
-Dcom.sun.jini.qa.harness.testJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar
-Dcom.sun.jini.qa.harness.runjiniserver=true
-Dcom.sun.jini.qa.harness.runkitserver=true
-Djava.security.properties=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/trust/dynamic-policy.properties
-Dcom.sun.jini.qa.harness.testhosts=
-Djava.util.logging.config.file=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\src\com\sun\jini\test\resources\qa1.logging
-Dcom.sun.jini.test.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.test.port=9082
-Dcom.sun.jini.qa.harness.policies=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/src/com/sun/jini/test/resources/jinitest.policy
'-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
com.sun.jini.qa.harness.MasterTest
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
com.sun.jini.qa.harness.TestException: -- blocked longer than
expected -- requested block = 60000 millisecond(s), actual block =
59999 millisecond(s)
at
com.sun.jini.test.spec.servicediscovery.lookup.LookupMinEqualsMax.verifyBlocking(LookupMinEqualsMax.java:288)
at
com.sun.jini.test.spec.servicediscovery.lookup.LookupMinEqualsMax.applyTestDef(LookupMinEqualsMax.java:120)
at
com.sun.jini.test.spec.servicediscovery.AbstractBaseTest.run(AbstractBaseTest.java:549)
at com.sun.jini.qa.harness.MasterTest.doTest(MasterTest.java:256)
at com.sun.jini.qa.harness.MasterTest.main(MasterTest.java:144)
TIME: 10:20:02 PM
Test process was destroyed and returned code 1
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
Test Failed: Test Failed: com.sun.jini.qa.harness.TestException: --
blocked longer than expected -- requested block = 60000
millisecond(s), actual block = 59999 millisecond(s)
Running
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
Running
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
Time is Tue May 07 10:40:30 EST 2013
Time is Tue May 07 10:40:30 EST 2013
Starting test in separate process with command:
Starting test in separate process with command:
'C:\Program Files\Java\jdk1.6.0_26\jre\bin\java'
-Djava.security.manager=org.apache.river.api.security.CombinerSecurityManager
-Djava.security.policy=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/policy/defaulttest.policy
-Djava.rmi.server.codebase=http://medusa:9082/qa1-servicediscovery-dl.jar
-cp
C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-platform.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\high-scale-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\custard-apple-1.0.3.jar
-ea -esa -client '-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
-Dcom.sun.jini.jsk.port=9080 -Dcom.sun.jini.qa.port=9081
-Dcom.sun.jini.jsk.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy
-Dcom.sun.jini.qa.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.qa.harness.harnessJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar
-Dcom.sun.jini.qa.harness.testJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar
-Dcom.sun.jini.qa.harness.runjiniserver=true
-Dcom.sun.jini.qa.harness.runkitserver=true
-Djava.security.properties=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/trust/dynamic-policy.properties
-Dcom.sun.jini.qa.harness.testhosts=
-Djava.util.logging.config.file=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\src\com\sun\jini\test\resources\qa1.logging
-Dcom.sun.jini.test.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.test.port=9082
-Dcom.sun.jini.qa.harness.policies=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/src/com/sun/jini/test/resources/jinitest.policy
'-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
com.sun.jini.qa.harness.MasterTest
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
'C:\Program Files\Java\jdk1.6.0_26\jre\bin\java'
-Djava.security.manager=org.apache.river.api.security.CombinerSecurityManager
-Djava.security.policy=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/policy/defaulttest.policy
-Djava.rmi.server.codebase=http://medusa:9082/qa1-servicediscovery-dl.jar
-cp
C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-platform.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\jsk-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\high-scale-lib.jar;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib\custard-apple-1.0.3.jar
-ea -esa -client '-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
-Dcom.sun.jini.jsk.port=9080 -Dcom.sun.jini.qa.port=9081
-Dcom.sun.jini.jsk.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy
-Dcom.sun.jini.qa.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.qa.harness.harnessJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jiniharness.jar
-Dcom.sun.jini.qa.harness.testJar=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib\jinitests.jar
-Dcom.sun.jini.qa.harness.runjiniserver=true
-Dcom.sun.jini.qa.harness.runkitserver=true
-Djava.security.properties=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/harness/trust/dynamic-policy.properties
-Dcom.sun.jini.qa.harness.testhosts=
-Djava.util.logging.config.file=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\src\com\sun\jini\test\resources\qa1.logging
-Dcom.sun.jini.test.home=C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa
-Dcom.sun.jini.test.port=9082
-Dcom.sun.jini.qa.harness.policies=file:/C:/Users/peter/Documents/NetBeansProjects/peterConcurrentPolicy/qa/src/com/sun/jini/test/resources/jinitest.policy
'-Djava.ext.dirs=C:\Program
Files\Java\jdk1.6.0_26\jre\lib\ext;C:\windows\Sun\Java\lib\ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\qa\lib-ext;C:\Users\peter\Documents\NetBeansProjects\peterConcurrentPolicy\lib-ext'
com.sun.jini.qa.harness.MasterTest
com/sun/jini/test/spec/servicediscovery/lookup/LookupMinLessMax.td
com.sun.jini.qa.harness.TestException: -- blocked longer than
expected -- requested block = 60000 millisecond(s), actual block =
59997 millisecond(s) wait error = 0
com.sun.jini.qa.harness.TestException: -- blocked longer than
expected -- requested block = 60000 millisecond(s), actual block =
59997 millisecond(s) wait error = 0
at
com.sun.jini.test.spec.servicediscovery.lookup.LookupMinEqualsMax.verifyBlocking(LookupMinEqualsMax.java:288)
at
com.sun.jini.test.spec.servicediscovery.lookup.LookupMinEqualsMax.applyTestDef(LookupMinEqualsMax.java:120)
at
com.sun.jini.test.spec.servicediscovery.AbstractBaseTest.run(AbstractBaseTest.java:549)
at com.sun.jini.qa.harness.MasterTest.doTest(MasterTest.java:256)
at com.sun.jini.qa.harness.MasterTest.main(MasterTest.java:144)
Relevant Section of Jini Specification:
SD.4.1.3 The |lookup| Method
The |lookup| method queries each available lookup service in the
managed set for service reference(s) that match criteria defined by
the entity that invokes this method. Entities typically employ this
method when they need infrequent access to services and when the cost
of making remote queries is outweighed by the overhead of maintaining
a local cache (for example, because of resource limitations).
The |lookup| method has four versions, each version falling into one
of two categories: those versions of this method that return a single
instance of |ServiceItem| and those versions that return a set of
service references as an array of |ServiceItem| objects.
Two arguments are common to all versions of this method: an instance
of |ServiceTemplate| and an instance of |ServiceItemFilter|.
Within each category, the versions of |lookup| differ only in whether
or not a particular version provides what is referred to as a "wait"
(or blocking) feature. That is, each category contains both a
non-blocking version of |lookup| which returns immediately when unable
to find the desired service, and a blocking version which returns only
after waiting a specified amount of time for the desired service to be
discovered. The particular version of |lookup| that an entity employs
is typically determined by the entity's intended usage pattern.
The descriptions that follow refer to all versions of the |lookup|
method, except where explicitly noted.
The |tmpl| argument and the |filter| argument both have semantics
identical to that defined for these arguments in the description of
the |createLookupCache| method above. In particular,
* A |null| reference value for the |tmpl| parameter is treated as
the equivalent of a "wildcarded" |ServiceTemplate|.
* If |null| is the value for the |filter| parameter, only template
matching will be employed to find the desired services.
* The effects of modifying the contents of the |tmpl| parameter
while the invocation is in progress are unpredictable and undefined.
If no service can be found that matches the desired criteria, then the
versions of |lookup| from the first category--those that return a
single instance of |ServiceItem|--will return |null|, whereas the
versions from the second category--those that return an array of
|ServiceItem| instances--will return an empty array.
The versions of |lookup| from the first category can be used in a
fashion similar to the first form of the |lookup| method defined in
the |ServiceRegistrar| interface described in the /Jini Lookup Service
Specification/. That is, an entity would typically invoke one of these
versions of |lookup| when it wishes to find a /single/ service
reference and the particular lookup service with which that service
reference is registered is unimportant to the entity.
Each version of |lookup| defined in the |ServiceDiscoveryManager|
differs with the corresponding version of |lookup| in
|ServiceRegistrar| in the following ways:
* The versions of |lookup| defined in the |ServiceDiscoveryManager|
query /multiple/ lookup services (the order in which the lookup
services are queried is dependent on the implementation).
* The versions of |lookup| defined in the |ServiceDiscoveryManager|
can apply additional selection criteria, in the form of a filter
object, when deciding whether a service reference found through
standard template matching should be returned to the entity.
The versions of |lookup| that return an array of |ServiceItem| objects
can be used in a fashion similar to the second form of |lookup|
defined in the |ServiceRegistrar| interface. That is, an entity would
typically invoke these versions of |lookup| when it wishes to find
/multiple/ service references that satisfy the input criteria. Each of
the versions of |lookup| that return an array of |ServiceItem| objects
takes as one of its arguments an |int| parameter, |maxMatches|, that
represents the maximum number of matches that should be returned. The
array returned by these methods will contain no more than |maxMatches|
service references, although it may contain fewer than that number.
As with the versions of |lookup| that return a single instance of
|ServiceItem|, multiple queries and filtering are also notable
differences between the second-category versions of this method and
their counterpart in |ServiceRegistrar|.
For each version of |lookup|, whenever a lookup service query returns
a |null| service reference, the filter is bypassed, and the service
reference is excluded from the return object. On the other hand, if
the query returns a non-|null| service reference in which the
associated array of attribute contains one or more |null| elements,
the filter is still applied and the service reference is included in
the return object.
Each version of |lookup| may be confronted with duplicate references
during a search for a service of interest. This is because the same
service may register with more than one lookup service in the managed
set. As with the cache, when a set of service references is returned
by |lookup|, each service reference in the return set will be unique
with respect to all other service references in the set, as determined
by the |equals| method provided by each reference.
If it is determined that a lookup service is unavailable (due to an
exception or some other non-fatal error) while interacting with a
lookup service from the managed set, all versions of |lookup| will
invoke the |discard| method on the instance of |DiscoveryManagement|
being employed by the |ServiceDiscoveryManager|. Doing so will result
in the unavailable lookup service being discarded and made eligible
for rediscovery.
Recall that the propagation of modifications to a service's attributes
across a set of lookup services typically occurs asynchronously. It is
for this reason that while invoking |lookup| to find a set of matching
services, it is possible that the set returned may contain multiple
references having the same service ID with different attributes. Note
that although this sort of inconsistent state can also occur if the
entity employs a cache, the cache will eventually reflect the correct
state.
The Blocking Feature of |lookup|
As noted above, each category contains a version of |lookup| that
provides a feature in which the entity can request that if the number
of service references found throughout the available lookup services
does not fall into a desired range, the method will wait a finite
period of time until either an acceptable minimum number of service
references are discovered or the specified time period has passed.
The versions of |lookup| providing this blocking feature each takes as
one of its parameters a value of type long that represents the number
of milliseconds to wait for the service to be discovered. In addition
to |RemoteException| (described previously for these methods), each of
these versions of |lookup| may throw an |InterruptedException|.
One of these blocking versions of |lookup| implicitly uses a value of
one for both the acceptable minimum and the allowable maximum number
of service references to discover. The other blocking version requires
that the entity specify the range through the |minMatches| and
|maxMatches| parameters, respectively.
Prior to blocking, each of these versions of |lookup| first queries
each available lookup service in an attempt to retrieve a satisfactory
number of matching services. Whether or not the method actually blocks
is dependent on how many matching service references are found during
the query process. Blocking occurs only if after querying /all/ of the
available lookup services, the number of matching services found is
less than the acceptable minimum. If the waiting period (measured from
when blocking first begins) passes before that minimum number of
service references is found, the method will return the service
references that have been discovered up to that point. If the waiting
period passes and no services have been found, |null| or an empty
array (depending on the version of |lookup|) will be returned.
If, after querying all of the available lookup services, the number of
services found to satisfy the desired criteria is greater than or
equal to the specified minimum but less than the specified maximum,
the method will return the currently discovered service references
without blocking. If the initial query process produces the desired
maximum number of service references, the method will return the
results immediately.
The blocking versions of |lookup| are quite useful to entities that
cannot proceed until such a service of interest is found. If a
non-positive value is input to the |waitDur| argument, then the method
will not wait. It will simply query the available lookup services and
employ the return semantics described above.
The values of the |minMatches| and |maxMatches| arguments must both be
positive, and |maxMatches| must be greater than or equal to
|minMatches|; otherwise, an |IllegalArgumentException| will be thrown.
The blocking versions of |lookup| make a concurrency guarantee with
respect to the discovery of new lookup services during the wait
period. That is, while waiting for the desired service reference(s) to
be discovered, if one or more of the targeted--but previously
unavailable--lookup services is discovered and added to the managed
set, those new lookup services will also be queried for the service(s)
of interest.
In addition, the blocking versions of |lookup| throw
|InterruptedException|. When an entity invokes either version with
valid parameters, the entity may decide during the wait period that it
no longer wishes to wait the entire period for the method to return.
Thus, while the method is blocking on the discovery of matching
service(s), it may be interrupted by invoking the |interrupt| method
from the |Thread| class. The intent of this mechanism is to allow the
entity to interrupt a blocking |lookup| in the same way it would a
sleeping thread.