[concurrency-interest] Non Blocking java.security.Policy - synchronized method is superclass.

Peter Firmstone peter.firmstone at zeus.net.au
Mon Jan 9 17:55:30 EST 2012


It's not causing any noticeable problems for me on 4 cpu's, looks like
it caused enough contention for someone to report it though, it's fixed
in Java 8.

We have a new SecurityManager that checks ProtectionDomain's in the
AccessControlContext in parallel, rather than in series, if there are
ten ProtectionDomain's on the stack, that would increase the number of
threads accessing the lock by a factor of ten.

So the potentials certainly there.

-Peter.

7093090 
*Votes* 0 
*Synopsis* Reduce synchronization in
java.security.Policy.getPolicyNoCheck * 
Category* java:classes_security 
*Reported Against* *Release Fixed* 8(b15) * 
State* 10-Fix Delivered, bug *Priority:* 2-High *Related Bugs* *Submit
Date* 20-SEP-2011 *Description*

java.security.Policy.getPolicyNoCheck() is synchronized which causes
some thread contention. 
Posted Date : 2011-09-20 23:44:03.0 

*Work Around*

N/A 

*Evaluation*

The fix involved adding an initialized flag to indicate when the
system-wide 
policy has been initialized and storing both the flag and the Policy
object in 
an AtomicReference. Then, I also used the double-check locking idiom to
avoid 
locking the Policy class when the Policy had already been initialized. 

Changeset: http://hg.openjdk.java.net/jdk8/tl/jdk/rev/1945abeb82a0
Posted Date : 2011-11-22 15:15:14.0 
On Tue, 2012-01-10 at 03:22, Nathan Reynolds wrote:
> > How much can this one synchronized method spoil scalability? 
> 
> Depends upon the workload.  Some workloads will never hit the method
> and hence will never have a scalability problem.  Other workloads will
> hit the method but not heavily enough to be a concern.  A contrived
> workload which launches a bunch of threads and simply call this method
> in a tight loop will not scale.  So, the question really is: Is there
> a real workload out there that won't scale due to this method?
> 
> Nathan Reynolds | Consulting Member of Technical Staff | 602.333.9091
> Oracle PSR Engineering | Server Technology
> 
> On 1/8/2012 4:40 AM, Peter Firmstone wrote: 
> > Appended is a new java.security.Policy implementation, it fully supports
> > the existing java policy syntax and accepts alternate PolicyParser's. 
> > 
> > All state is immutable, except for 2 volatile references, referents
> > replaced, not mutated, when the policy is updated.  One referent is an
> > array containing PermissionGrant's (interface for immutable object
> > representing a grant statement in a policy), the second a
> > PermissionCollection containing the Policy Permissions.  The array is
> > never mutated after creation, a reference to the array is copied before
> > accessing the array or any array methods. 
> > 
> > The policy creates PermissionCollection's on demand for checking,
> > Permission's are ordered using a PermissionComparator to ensure that for
> > example, wildcard SocketPermission's are checked first, to avoid
> > unnecessary DNS lookups.  Only the permission being checked and any
> > UnresolvedPermission's are added to the PermissionCollection, limiting
> > the size of the objects created. 
> > 
> > In existing policy implementations PermissionCollection's perform
> > blocking operations. 
> > 
> > Also, after parsing policy files, PermissionGrant implementations avoid
> > the need to open files or network connections to confirm URL's, eg
> > CodeSource.implies is not called, but instead reimplemented using URI. 
> > 
> > Will this scale?  There but one smell: 
> > 
> > ProtectionDomain uses a synchronized method Policy.getPolicyNoCheck(),
> > but this only retrieves a reference on 99% of occasions. 
> > 
> > For every permission check, the stack access control context is
> > retrieved, every ProtectionDomain on the stack must be checked,
> > ProtectionDomain's must call getPolicyNoCheck() to call
> > Policy.implies(ProtectionDomain domain, Permission permission). 
> > 
> > To make this worse, I've got a SecurityManager that divides the
> > ProtectionDomain.implies() calls into tasks and submits them to an
> > executor (if there are 4 or more PD's in a context).  The
> > SecurityManager is also non blocking, at least it will be when I use the
> > new ConcurrentHashMap for the checked permission cache (avoids repeated
> > security checks), for now the cache is implemented using the existing
> > ConcurrentHashMap, but is mostly read in any case.  (P.S. This is the
> > cache I'm using the Reference Collection's for.) 
> > 
> > How much can this one synchronized method spoil scalability? 
> > 
> > Cheers & thanks in advance, 
> > 
> > Peter. 
> > 
> > 
> > 
> > /* 
> > *  Licensed to the Apache Software Foundation (ASF) under one or more 
> > *  contributor license agreements.  See the NOTICE file distributed with
> > *  this work for additional information regarding copyright ownership. 
> > *  The ASF licenses this file to You under the Apache License, Version
> > 2.0 
> > *  (the "License"); you may not use this file except in compliance with 
> > *  the License.  You may obtain a copy of the License at 
> > * 
> > *     http://www.apache.org/licenses/LICENSE-2.0
> > * 
> > *  Unless required by applicable law or agreed to in writing, software 
> > *  distributed under the License is distributed on an "AS IS" BASIS, 
> > *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> > implied. 
> > *  See the License for the specific language governing permissions and 
> > *  limitations under the License. 
> > */ 
> > 
> > /** 
> >  * Default Policy implementation taken from Apache Harmony, refactored
> > for 
> >  * concurrency. 
> >  * 
> >  * @author Alexey V. Varlamov 
> >  * @author Peter Firmstone 
> >  * @version $Revision$ 
> >  */ 
> > 
> > package net.jini.security.policy; 
> > 
> > import java.io.File; 
> > import java.net.URL; 
> > import java.security.AccessController; 
> > import java.security.AllPermission; 
> > import java.security.CodeSource; 
> > import java.security.Guard; 
> > import java.security.Permission; 
> > import java.security.PermissionCollection; 
> > import java.security.Permissions; 
> > import java.security.Policy; 
> > import java.security.PrivilegedActionException; 
> > import java.security.PrivilegedExceptionAction; 
> > import java.security.ProtectionDomain; 
> > import java.security.SecurityPermission; 
> > import java.security.UnresolvedPermission; 
> > import java.util.ArrayList; 
> > import java.util.Collection; 
> > import java.util.Enumeration; 
> > import java.util.Iterator; 
> > import java.util.List; 
> > import java.util.NavigableSet; 
> > import java.util.Properties; 
> > import java.util.TreeSet; 
> > import net.jini.security.PermissionComparator; 
> > import org.apache.river.api.security.PermissionGrant; 
> > import org.apache.river.impl.security.policy.util.DefaultPolicyParser; 
> > import org.apache.river.impl.security.policy.util.PolicyParser; 
> > import org.apache.river.impl.security.policy.util.PolicyUtils; 
> > 
> > 
> > /** 
> > * Concurrent Policy implementation based on policy configuration files, 
> > * it is intended to provide concurrent implies() for greatly improved 
> > * throughput at the expense of single threaded performance. 
> > * 
> > * Set the following system properties to use this Policy instead of the 
> > * built in Java sun.security.provider.PolicyFile: 
> > * * net.jini.security.policy.PolicyFileProvider.basePolicyClass = 
> > * org.apache.river.security.concurrent.ConcurrentPolicyFile 
> > * 
> > * 
> > * This 
> > * implementation recognizes text files, consisting of clauses with the 
> > * following syntax: 
> > * 
> > * <pre> 
> > * keystore "some_keystore_url" [, "keystore_type"]; 
> > * </pre> 
> > <pre> 
> > * grant [SignedBy "signer_names"] [, CodeBase "URL"]
> > *  [, Principal [principal_class_name] "principal_name"] 
> > *  [, Principal [principal_class_name] "principal_name"] ... {
> > *  permission permission_class_name [ "target_name" ] [,
> > "action"] 
> > *  [, SignedBy "signer_names"]; 
> > *  permission ... 
> > *  }; 
> > * * </pre> 
> > * 
> > * The <i>keystore </i> clause specifies reference to a keystore, which
> > is a 
> > * database of private keys and their associated digital certificates.
> > The 
> > * keystore is used to look up the certificates of signers specified in
> > the 
> > * <i>grant </i> entries of the file. The policy file can contain any
> > number of 
> > * <i>keystore </i> entries which can appear at any ordinal position.
> > However, 
> > * only the first successfully loaded keystore is used, others are
> > ignored. The 
> > * keystore must be specified if some grant clause refers to a
> > certificate's 
> > * alias. <br> 
> > * The <i>grant </i> clause associates a CodeSource (consisting of an URL
> > and a 
> > * set of certificates) of some executable code with a set of Permissions
> > which 
> > * should be granted to the code. So, the CodeSource is defined by values
> > of 
> > * <i>CodeBase </i> and <i>SignedBy </i> fields. The <i>CodeBase </i>
> > value must 
> > * be in URL format, while <i>SignedBy </i> value is a (comma-separated
> > list of) 
> > * alias(es) to keystore certificates. These fields can be omitted to
> > denote any 
> > * codebase and any signers (including case of unsigned code),
> > respectively. 
> > * <br> 
> > * Also, the code may be required to be executed on behalf of some
> > Principals 
> > * (in other words, code's ProtectionDomain must have the array of
> > Principals 
> > * associated) in order to possess the Permissions. This fact is
> > indicated by 
> > * specifying one or more <i>Principal </i> fields in the <i>grant </i>
> > clause. 
> > * Each Principal is specified as class/name pair; name and class can be
> > either 
> > * concrete value or wildcard <i>* </i>. As a special case, the class
> > value may 
> > * be omitted and then the name is treated as an alias to X.509
> > Certificate, and 
> > * the Principal is assumed to be javax.security.auth.x500.X500Principal
> > with a 
> > * name of subject's distinguished name from the certificate. <br> 
> > * The order between the <i>CodeBase </i>, <i>SignedBy </i>, and
> > <i>Principal 
> > * </i> fields does not matter. The policy file can contain any number of
> > grant 
> > * clauses. <br> 
> > * Each <i>grant </i> clause must contain one or more <i>permission </i>
> > entry. 
> > * The permission entry consist of a fully qualified class name along
> > with 
> > * optional <i>name </i>, <i>actions </i> and <i>signedby </i> values.
> > Name and 
> > * actions are arguments to the corresponding constructor of the
> > permission 
> > * class. SignedBy value represents the keystore alias(es) to
> > certificate(s) 
> > * used to sign the permission class. That is, this permission entry is 
> > * effective (i.e., access control permission will be granted based on
> > this 
> > * entry) only if the bytecode implementation of permission class is
> > verified to 
> > * be correctly signed by the said alias(es). <br> 
> > * <br> 
> > * The policy content may be parameterized via property expansion.
> > Namely, 
> > * expressions like <i>${key} </i> are replaced by values of
> > corresponding 
> > * system properties. Also, the special <i>slash </i> key (i.e. ${/}) is 
> > * supported, it is a shortcut to "file.separator" key.
> > Property 
> > * expansion is performed anywhere a double quoted string is allowed in
> > the 
> > * policy file. However, this feature is controlled by security
> > properties and 
> > * should be turned on by setting "policy.expandProperties"
> > property 
> > * to <i>true </i>. <br> 
> > * If property expansion fails (due to a missing key), a corresponding
> > entry is 
> > * ignored. For fields of <i>keystore </i> and <i>grant </i> clauses, the
> > whole 
> > * clause is ignored, and for <i>permission </i> entry, only that entry
> > is 
> > * ignored. <br> 
> > * <br> 
> > * The policy also supports generalized expansion in permissions names,
> > of 
> > * expressions like <i>${{protocol:data}} </i>. Currently the following 
> > * protocols supported: 
> > * <dl> 
> > * <dt>self 
> > * <dd>Denotes substitution to a principal information of the parental
> > Grant 
> > * entry. Replaced by a space-separated list of resolved Principals
> > (including 
> > * wildcarded), each formatted as <i>class "name" </i>. If
> > parental 
> > * Grant entry has no Principals, the permission is ignored. 
> > * <dt>alias: <i>name </i> 
> > * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore
> > has an 
> > * X.509 certificate associated with the specified name, then replaced by
> > * <i>javax.security.auth.x500.X500Principal " <i>DN </i>" </i>
> > * string, where <i>DN </i> is a certificate's subject distinguished
> > name. 
> > * </dl> 
> > * <br> 
> > * 
> > */ 
> > 
> > public class ConcurrentPolicyFile extends Policy implements
> > ConcurrentPolicy { 
> > 
> >    /** 
> >     * System property for dynamically added policy location. 
> >     */ 
> >    private static final String JAVA_SECURITY_POLICY =
> > "java.security.policy"; //$NON-NLS-1$ 
> > 
> >    /** 
> >     * Prefix for numbered Policy locations specified in
> > security.properties. 
> >     */ 
> >    private static final String POLICY_URL_PREFIX = "policy.url.";
> > //$NON-NLS-1$ 
> >      // Reference must be defensively copied before access, once
> > published, never mutated. 
> >    private volatile PermissionGrant [] grantArray; 
> >      // A specific parser for a particular policy file format. 
> >    private final PolicyParser parser; 
> >      private static final Guard guard = new
> > SecurityPermission("getPolicy"); 
> >      private final ProtectionDomain myDomain; 
> >      // reference must be defensively copied before access, once
> > published, never mutated. 
> >    private volatile PermissionCollection myPermissions; 
> >      /** 
> >     * Default constructor, equivalent to 
> >     * <code>ConcurrentPolicyFile(new DefaultPolicyParser())</code>. 
> >     */ 
> >    public ConcurrentPolicyFile() throws PolicyInitializationException { 
> >        this(new DefaultPolicyParser()); 
> >    } 
> > 
> >    /** 
> >     * Extension constructor for plugging-in a custom parser. 
> >     * @param dpr 
> >     */ 
> >    protected ConcurrentPolicyFile(PolicyParser dpr) throws
> > PolicyInitializationException { 
> >        guard.checkGuard(null); 
> >        parser = dpr; 
> >        myDomain = this.getClass().getProtectionDomain(); 
> >        /* 
> >         * The bootstrap policy makes implies decisions until this
> > constructor 
> >         * has returned.  We don't need to lock. 
> >         */ 
> >        try { 
> >            // Bug 4911907, do we need to do anything more? 
> >            // The permissions for this domain must be retrieved before 
> >            // construction is complete and this policy takes over. 
> >            initialize(); // Instantiates myPermissions. 
> >        } catch (SecurityException e){ 
> >            throw e; 
> >        } catch (Exception e){ 
> >            throw new PolicyInitializationException("PolicyInitialization
> > failed", e); 
> >        } 
> >    } 
> >      private PermissionCollection convert(NavigableSet<Permission>
> > permissions){ 
> >        PermissionCollection pc = new Permissions(); 
> >        // The descending iterator is for SocketPermission. 
> >        Iterator<Permission> it = permissions.descendingIterator(); 
> >        while (it.hasNext()) { 
> >            pc.add(it.next()); 
> >        } 
> >        return pc; 
> >    } 
> > 
> >    /** 
> >     * Returns collection of permissions allowed for the domain 
> >     * according to the policy. The evaluated characteristics of the 
> >     * domain are it's codesource and principals; they are assumed 
> >     * to be <code>null</code> if the domain is <code>null</code>. 
> >     * 
> >     * Each PermissionCollection returned is a unique instance. 
> >     * 
> >     * @param pd ProtectionDomain 
> >     * @see ProtectionDomain 
> >     */ 
> >    @Override 
> >    public PermissionCollection getPermissions(ProtectionDomain pd) { 
> >        NavigableSet<Permission> perms = new TreeSet<Permission>(new
> > PermissionComparator()); 
> >        PermissionGrant [] grantRefCopy = grantArray; 
> >        int l = grantRefCopy.length; 
> >        for ( int j =0; j < l; j++ ){ 
> >            PermissionGrant ge = grantRefCopy[j]; 
> >            if (ge.implies(pd)){ 
> >                if (ge.isPrivileged()){// Don't stuff around finish early
> > if you can. 
> >                    PermissionCollection pc = new Permissions(); 
> >                    pc.add(new AllPermission()); 
> >                    return pc; 
> >                } 
> >                Collection<Permission> c = ge.getPermissions(); 
> >                Iterator<Permission> i = c.iterator(); 
> >                while (i.hasNext()){ 
> >                    Permission p = i.next(); 
> >                    perms.add(p); 
> >                } 
> >            } 
> >        } 
> >        // Don't forget to merge the static Permissions. 
> >        PermissionCollection staticPC = null; 
> >        if (pd != null) { 
> >            staticPC = pd.getPermissions(); 
> >            if (staticPC != null){ 
> >                Enumeration<Permission> e = staticPC.elements(); 
> >                while (e.hasMoreElements()){ 
> >                    Permission p = e.nextElement(); 
> >                    if (p instanceof AllPermission) { 
> >                        PermissionCollection pc = new Permissions(); 
> >                        pc.add(p); 
> >                        return pc; 
> >                    } 
> >                    perms.add(p); 
> >                } 
> >            } 
> >        } 
> >        return convert(perms); 
> >    } 
> > 
> >    /** 
> >     * Returns collection of permissions allowed for the codesource 
> >     * according to the policy. 
> >     * The evaluation assumes that current principals are undefined. 
> >     * 
> >     * This returns a java.security.Permissions collection, which allows 
> >     * ProtectionDomain to optimise for the AllPermission case, which
> > avoids 
> >     * unnecessarily consulting the policy. 
> >     * 
> >     * If constructed with the four argument constructor,
> > ProtectionDomain.implies 
> >     * first consults the Policy, then it's own internal Permissions
> > collection, 
> >     * unless it has AllPermission, in which case it returns true without
> >     * consulting the policy. 
> >     * 
> >     * @param cs CodeSource 
> >     * @see CodeSource 
> >     */ 
> >    @Override 
> >    public PermissionCollection getPermissions(CodeSource cs) { 
> >        if (cs == null) throw new NullPointerException("CodeSource cannot
> > be null"); 
> >        NavigableSet<Permission> perms = new TreeSet<Permission>(new
> > PermissionComparator()); 
> >        // for ProtectionDomain AllPermission optimisation. 
> >        PermissionGrant [] grantRefCopy = grantArray; 
> >        int l = grantRefCopy.length; 
> >        for ( int j =0; j < l; j++ ){ 
> >            PermissionGrant ge = grantRefCopy[j]; 
> >            if (ge.implies(cs, null)){ // No Principal's 
> >                if (ge.isPrivileged()){// Don't stuff around finish early
> > if you can. 
> >                    PermissionCollection pc = new Permissions(); 
> >                    pc.add(new AllPermission()); 
> >                    return pc; 
> >                } 
> >                Collection<Permission> c = ge.getPermissions(); 
> >                Iterator<Permission> i = c.iterator(); 
> >                while (i.hasNext()){ 
> >                    Permission p = i.next(); 
> >                    perms.add(p); 
> >                } 
> >            } 
> >        } 
> >        return convert(perms); 
> >    } 
> >      @Override 
> >    public boolean implies(ProtectionDomain domain, Permission
> > permission) { 
> >        if (permission == null) throw new
> > NullPointerException("permission not allowed to be null"); 
> >        if (domain == myDomain) { 
> >            PermissionCollection pc = myPermissions; 
> >            return pc.implies(permission); 
> >        } 
> >        Class klass = permission.getClass(); 
> >        // Need to have a list of Permission's we can sort if permission
> > is SocketPermission. 
> >        NavigableSet<Permission> perms = new TreeSet<Permission>(new
> > PermissionComparator()); 
> >        PermissionGrant [] grantRefCopy = grantArray; 
> >        int l = grantRefCopy.length; 
> >        for ( int j =0; j < l; j++ ){ 
> >            PermissionGrant ge = grantRefCopy[j]; 
> >            if (ge.implies(domain)){ 
> >                if (ge.isPrivileged()) return true; // Don't stuff around
> > finish early if you can. 
> >                Collection<Permission> c = ge.getPermissions(); 
> >                Iterator<Permission> i = c.iterator(); 
> >                while (i.hasNext()){ 
> >                    Permission p = i.next(); 
> >                    // Don't make it larger than necessary. 
> >                    if (klass.isInstance(permission) || permission
> > instanceof UnresolvedPermission){ 
> >                        perms.add(p); 
> >                    } 
> >                } 
> >            } 
> >        } 
> >        // Don't forget to merge the static Permissions. 
> >        PermissionCollection staticPC = null; 
> >        if (domain != null) { 
> >            staticPC =domain.getPermissions(); 
> >            if (staticPC != null){ 
> >                Enumeration<Permission> e = staticPC.elements(); 
> >                while (e.hasMoreElements()){ 
> >                    Permission p = e.nextElement(); 
> >                    // return early if possible. 
> >                    if (p instanceof AllPermission ) return true; 
> >                    // Don't make it larger than necessary, but don't
> > worry about duplicates either. 
> >                    if (klass.isInstance(permission) || permission
> > instanceof UnresolvedPermission){ 
> >                        perms.add(p); 
> >                    } 
> >                } 
> >            } 
> >        } 
> >        return convert(perms).implies(permission); 
> >    } 
> > 
> >    /** 
> >     * Gets fresh list of locations and tries to load all of them in
> > sequence; 
> >     * failed loads are ignored. After processing all locations, old
> > policy 
> >     * settings are discarded and new ones come into force. <br> 
> >     * 
> >     * @see PolicyUtils#getPolicyURLs(Properties, String, String) 
> >     */ 
> >    @Override 
> >    public void refresh() { 
> >        try { 
> >            initialize(); 
> >        } catch (Exception ex) { 
> >            System.err.println(ex); 
> >        } 
> >    } 
> >      private void initialize() throws Exception{ 
> >        try { 
> >            Collection<PermissionGrant> fresh =
> > AccessController.doPrivileged( 
> >                new
> > PrivilegedExceptionAction<Collection<PermissionGrant>>(){ 
> >                    public Collection<PermissionGrant> run() throws
> > SecurityException { 
> >                        Collection<PermissionGrant> fresh = new
> > ArrayList<PermissionGrant>(120); 
> >                        Properties system = System.getProperties(); 
> >                        system.setProperty("/", File.separator);
> > //$NON-NLS-1$ 
> >                        URL[] policyLocations =
> > PolicyUtils.getPolicyURLs(system, 
> >                                                         
> > JAVA_SECURITY_POLICY, 
> >                                                         
> > POLICY_URL_PREFIX); 
> >                        int l = policyLocations.length; 
> >                        for (int i = 0; i < l; i++) { 
> >                            //TODO debug log 
> > //                                System.err.println("Parsing policy
> > file: " + policyLocations[i]); 
> >                            try { 
> >                                Collection<PermissionGrant> pc = null; 
> >                                pc = parser.parse(policyLocations[i],
> > system); 
> >                                fresh.addAll(pc); 
> >                            } catch (Exception e){ 
> >                                // It's best to let a SecurityException
> > bubble up 
> >                                // in case there is a problem with our
> > policy configuration 
> >                                // or implementation. 
> >                                if ( e instanceof SecurityException ) { 
> >                                    e.printStackTrace(System.out); 
> >                                    throw (SecurityException) e; 
> >                                } 
> >                                // ignore. 
> >                            } 
> >                        } 
> >                        return fresh; 
> >                    } 
> >                } 
> >            ); 
> >            // Volatile reference, publish after mutation complete. 
> >            grantArray = fresh.toArray(new
> > PermissionGrant[fresh.size()]); 
> >            myPermissions = getPermissions(myDomain); 
> >        }catch (PrivilegedActionException e){ 
> >            Throwable t = e.getCause(); 
> >            if ( t instanceof Exception ) throw (Exception) t; 
> >            throw e; 
> >        } 
> >    } 
> > 
> >    public boolean isConcurrent() { 
> >        return true; 
> >    } 
> > 
> >    public PermissionGrant[] getPermissionGrants() { 
> >        PermissionGrant [] grants = grantArray; // copy volatile
> > reference target. 
> >        return grants.clone(); 
> >    } 
> >      public PermissionGrant[] getPermissionGrants(ProtectionDomain pd) {
> >        PermissionGrant [] grants = grantArray; // copy volatile
> > reference target. 
> >        int l = grants.length; 
> >        List<PermissionGrant> applicable = new
> > ArrayList<PermissionGrant>(l); // Always too large, never too small. 
> >        for (int i =0; i < l; i++){ 
> >            if (grants[i].implies(pd)){ 
> >                applicable.add(grants[i]); 
> >            } 
> >        } 
> >        return applicable.toArray(new
> > PermissionGrant[applicable.size()]); 
> >    } 
> > 
> > } 
> > 
> > _______________________________________________
> > Concurrency-interest mailing list
> > Concurrency-interest at cs.oswego.edu
> > http://cs.oswego.edu/mailman/listinfo/concurrency-interest



More information about the Concurrency-interest mailing list