[ 
https://issues.apache.org/jira/browse/FELIX-1841?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12773264#action_12773264
 ] 

Pierre De Rop commented on FELIX-1841:
--------------------------------------

Felix,

I'm not sure, but it seems that the issue comes from the 
DependencyManager.ungetService(), which is wrongly invoked
from the DependencyManager.serviceRemoved() method:

Let me try to explain:

- The service "Service" is injected to "Client" service, which do have a (1..1) 
dependency over "Service".
- Then the "Service" service properties are updated.
- So, in DependencyManager.java, the serviceChanged is called with a MODIFIED 
event:

            case ServiceEvent.MODIFIED:
                m_componentManager.log( LogService.LOG_DEBUG, "Dependency 
Manager: Updating {0}", new Object[]
                    { serviceString }, null );

                // remove the service first
                // only continue with further event handling if the service
                // removal did not cause the component to be deactivated
                if ( serviceRemoved( ref ) )
                {
                    // recalculate the number of services matching the filter
                    // because we don't know whether this service previously 
matched
                    // or not
                    ServiceReference refs[] = getFrameworkServiceReferences();
                    m_size = ( refs == null ) ? 0 : refs.length;

                    // now try to bind the service - if it matches the target 
filter
                    // without recalculating the size (already done).
                    if ( targetFilterMatch( ref ) )
                    {
                        serviceAdded( ref );
                    }
                }

- Here, the serviceRemoved method is then invoked, which ends up in doing a 
bound service replacement (line 358):

            else
            {

                // try to bind a replacement service first if this is a unary
                // cardinality reference and a replacement is available.
                if ( !m_dependencyMetadata.isMultiple() )
                {
                    // if the dependency is mandatory and no replacement is
                    // available, bind returns false and we deactivate
                    if ( !bind() )
                    {
                        m_componentManager
                            .log(
                                LogService.LOG_DEBUG,
                                "Dependency Manager: Deactivating component due 
to mandatory dependency on {0}/{1} not satisfied",
                                new Object[]
                                    { m_dependencyMetadata.getName(), 
m_dependencyMetadata.getInterface() }, null );
                        m_componentManager.deactivateInternal( 
ComponentConstants.DEACTIVATION_REASON_REFERENCE );

                        // required service could not be replaced, component
                        // is deactivated and we are done
                        return false;
                    }
                }

                // call the unbind method if one is defined
                if ( m_dependencyMetadata.getUnbind() != null )
                {
                    invokeUnbindMethod( reference );
                }

                // make sure the service is returned
                ungetService( reference );
            }

- the "if (!bind())" line will re-bind "Client" to the same "Service" service, 
as "Service" properties are just modified (and "Service" is still present)
- The invokeUnbindMethod will then unbind the same service
- And the ungetService method will remove the bound service from the m_bound 
map -> but I think that this is the issue, because the map should contain the 
same "Service" service, which has just been re-bound, in the bind() method.
- So, let's follow up with the issue: the "serviceAdded" method is then called 
from the serviceChanged() method.
- The serviceAdded method will then attempt to bind the same service, line 268 
(but we already did it from the serviceRemoved method), and here, the isBound() 
returns false because the m_bound map has been wrongly emptied previously.

                // multiple bindings or not bound at all yet
                if ( m_dependencyMetadata.isMultiple() || !isBound() )
                {
                    // bind the service, getting it if required
                    invokeBindMethod( reference );
                }
            }


So, I replaced the else bloc (line 358) with the following patch (which is 
attached to this issue): and the problem seems to disappear:


            // dynamic dependency, multiple or single but this service is the 
bound one
            else
            {
                boolean doUnget = true;
                
                // try to bind a replacement service first if this is a unary
                // cardinality reference and a replacement is available.
                if ( !m_dependencyMetadata.isMultiple() )
                {
                    // if the dependency is mandatory and no replacement is
                    // available, bind returns false and we deactivate
                    if ( !bind() )
                    {
                        m_componentManager
                            .log(
                                LogService.LOG_DEBUG,
                                "Dependency Manager: Deactivating component due 
to mandatory dependency on {0}/{1} not satisfied",
                                new Object[]
                                    { m_dependencyMetadata.getName(), 
m_dependencyMetadata.getInterface() }, null );
                        m_componentManager.deactivateInternal( 
ComponentConstants.DEACTIVATION_REASON_REFERENCE );

                        // required service could not be replaced, component
                        // is deactivated and we are done
                        return false;
                    }
                    
                    // Check if we have been rebound to the same (modified) 
service.
                    if (m_bound.size() == 1 && getBoundService( reference ) != 
null)  {
                        // the same (modified) service has been re-bound: don't 
unget.
                        doUnget = false;
                    }
                }

                // call the unbind method if one is defined
                if ( m_dependencyMetadata.getUnbind() != null )
                {
                    invokeUnbindMethod( reference );
                }

                // make sure the service is returned.
                if (doUnget) {
                    ungetService( reference );
                }
            }


Does this make sense to you ? Am I correct or did I miss something else ?


Thanks;
/pierre


> SCR invokes bind method twice when dependency service properties are modified
> -----------------------------------------------------------------------------
>
>                 Key: FELIX-1841
>                 URL: https://issues.apache.org/jira/browse/FELIX-1841
>             Project: Felix
>          Issue Type: Bug
>          Components: Declarative Services (SCR)
>            Reporter: Pierre De Rop
>            Priority: Minor
>
> It seems that when a service S is depending on another service D, and when D 
> service properties are modified (using ServiceRegistration.setProperties 
> method), then D is 
> re-bound twice into the using service S.
> For example, I have a service "Client" has a "1..1" dependency over "Service":
> <?xml version="1.0" encoding="UTF-8"?>
> <component name="Client">
>   <implementation class="client.Client"/>
>   <reference name="SERVICE" 
>     interface="service.Service"
>     policy="dynamic"
>     cardinality="1..1"
>     target="(foo=bar)"
>     bind="bind"/>
> </component>
> public class Client {
>   protected void bind(Service s) {
>     System.out.println("Client:: bound Service : " + s);
>     Thread.dumpStack();
>   }
> }
> I have another bundle which provide the "Service" dependency, and sometimes, 
> the "Service" properties are modified like this:
>         ServiceRegistration reg ...
>         reg.setProperties(...)
>  
> So, when the setProperties takes place, "Client" is re-bound twice with the 
> service "Service".
> Indeed, in DependencyManager, when a ServiceEvent.MODIFIED event. is caught, 
> the following code is invoked (line 170):
>             case ServiceEvent.MODIFIED:
>                 m_componentManager.log( LogService.LOG_DEBUG, "Dependency 
> Manager: Updating {0}", new Object[]
>                     { serviceString }, null );
>                 // remove the service first
>                 // only continue with further event handling if the service
>                 // removal did not cause the component to be deactivated
>                 if ( serviceRemoved( ref ) )
>                 {
>                     // recalculate the number of services matching the filter
>                     // because we don't know whether this service previously 
> matched
>                     // or not
>                     ServiceReference refs[] = getFrameworkServiceReferences();
>                     m_size = ( refs == null ) ? 0 : refs.length;
>                     // now try to bind the service - if it matches the target 
> filter
>                     // without recalculating the size (already done).
>                     if ( targetFilterMatch( ref ) )
>                     {
>                         serviceAdded( ref );
>                     }
>                 }
>                 break;
> So, the service is first re-bound to the Client, when the serviceRemoved() 
> method is invoked (it's a bound service replacement, I guess).
> But the problem here is that the modified service is also re-bound, when 
> serviceAdded is invoked (line 189).
> Don't you think that this is a bug and the service should be re-bound only 
> once, not twice ?
> Here is the first stacktrace of the first bind:
> java.lang.Exception: Stack trace
>         at java.lang.Thread.dumpStack(Thread.java:1158)
>         at client.Client.bind(Client.java:13)
>         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>         at 
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
>         at 
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
>         at java.lang.reflect.Method.invoke(Method.java:592)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.invokeMethod(BaseMethod.java:213)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.access$500(BaseMethod.java:38)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod$Resolved.invoke(BaseMethod.java:542)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.invoke(BaseMethod.java:434)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.invokeBindMethod(DependencyManager.java:948)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.bind(DependencyManager.java:884)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.serviceRemoved(DependencyManager.java:367)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.serviceChanged(DependencyManager.java:177)
>         at 
> org.apache.felix.framework.util.EventDispatcher.invokeServiceListenerCallback(EventDispatcher.java:878)
>         at 
> org.apache.felix.framework.util.EventDispatcher.fireEventImmediately(EventDispatcher.java:732)
>         at 
> org.apache.felix.framework.util.EventDispatcher.fireServiceEvent(EventDispatcher.java:662)
>         at org.apache.felix.framework.Felix.fireServiceEvent(Felix.java:3587)
>         at org.apache.felix.framework.Felix.access$000(Felix.java:40)
>         at org.apache.felix.framework.Felix$1.serviceChanged(Felix.java:625)
>         at 
> org.apache.felix.framework.ServiceRegistry.servicePropertiesModified(ServiceRegistry.java:505)
>         at 
> org.apache.felix.framework.ServiceRegistrationImpl.setProperties(ServiceRegistrationImpl.java:116)
>         at service.impl.ServiceFactory.run(ServiceFactory.java:48)
>         at java.lang.Thread.run(Thread.java:595)
> and here is the second stacktrace, when Service is re-bound:
> Client: bound Service : service.impl.servicei...@142a80d
> java.lang.Exception: Stack trace
>         at java.lang.Thread.dumpStack(Thread.java:1158)
>         at client.Client.bind(Client.java:13)
>         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>         at 
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
>         at 
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
>         at java.lang.reflect.Method.invoke(Method.java:592)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.invokeMethod(BaseMethod.java:213)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.access$500(BaseMethod.java:38)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod$Resolved.invoke(BaseMethod.java:542)
>         at 
> org.apache.felix.scr.impl.helper.BaseMethod.invoke(BaseMethod.java:434)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.invokeBindMethod(DependencyManager.java:948)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.serviceAdded(DependencyManager.java:271)
>         at 
> org.apache.felix.scr.impl.manager.DependencyManager.serviceChanged(DependencyManager.java:189)
>         at 
> org.apache.felix.framework.util.EventDispatcher.invokeServiceListenerCallback(EventDispatcher.java:878)
>         at 
> org.apache.felix.framework.util.EventDispatcher.fireEventImmediately(EventDispatcher.java:732)
>         at 
> org.apache.felix.framework.util.EventDispatcher.fireServiceEvent(EventDispatcher.java:662)
>         at org.apache.felix.framework.Felix.fireServiceEvent(Felix.java:3587)
>         at org.apache.felix.framework.Felix.access$000(Felix.java:40)
>         at org.apache.felix.framework.Felix$1.serviceChanged(Felix.java:625)
>         at 
> org.apache.felix.framework.ServiceRegistry.servicePropertiesModified(ServiceRegistry.java:505)
>         at 
> org.apache.felix.framework.ServiceRegistrationImpl.setProperties(ServiceRegistrationImpl.java:116)
>         at service.impl.ServiceFactory.run(ServiceFactory.java:48)
>         at java.lang.Thread.run(Thread.java:595)

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.

Reply via email to