XmlPlexusConfigurationConcurrencyBenchmark.java

/*
 * 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.
 */
package org.apache.maven.internal.xml;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.maven.api.xml.XmlNode;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Group;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
 * JMH benchmarks for testing thread safety and concurrent performance.
 *
 * This benchmark specifically tests the thread safety improvements in the new implementation
 * by running concurrent operations that would cause race conditions in the old version.
 */
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Threads(4) // Test with multiple threads to expose race conditions
public class XmlPlexusConfigurationConcurrencyBenchmark {

    private XmlNode testNode;
    private PlexusConfiguration configOld;
    private PlexusConfiguration configNew;

    @Setup
    public void setup() {
        testNode = createTestNode();
        configOld = new XmlPlexusConfigurationOld(testNode);
        configNew = new XmlPlexusConfiguration(testNode);
    }

    /**
     * Test concurrent child access with old implementation
     * This may expose race conditions and inconsistent behavior
     */
    @Benchmark
    @Group("concurrentAccessOld")
    public void concurrentChildAccessOld(Blackhole bh) {
        try {
            for (int i = 0; i < configOld.getChildCount(); i++) {
                PlexusConfiguration child = configOld.getChild(i);
                bh.consume(child.getName());
                bh.consume(child.getValue());

                // Access nested children to stress the implementation
                for (int j = 0; j < child.getChildCount(); j++) {
                    PlexusConfiguration nested = child.getChild(j);
                    bh.consume(nested.getName());
                    bh.consume(nested.getValue());
                }
            }
        } catch (Exception e) {
            // Old implementation may throw exceptions under concurrent access
            bh.consume(e);
        }
    }

    /**
     * Test concurrent child access with new implementation
     * This should be thread-safe and perform consistently
     */
    @Benchmark
    @Group("concurrentAccessNew")
    public void concurrentChildAccessNew(Blackhole bh) {
        for (int i = 0; i < configNew.getChildCount(); i++) {
            PlexusConfiguration child = configNew.getChild(i);
            bh.consume(child.getName());
            bh.consume(child.getValue());

            // Access nested children to stress the implementation
            for (int j = 0; j < child.getChildCount(); j++) {
                PlexusConfiguration nested = child.getChild(j);
                bh.consume(nested.getName());
                bh.consume(nested.getValue());
            }
        }
    }

    /**
     * Test concurrent construction and access with old implementation
     */
    @Benchmark
    public void concurrentConstructionOld(Blackhole bh) {
        try {
            PlexusConfiguration config = new XmlPlexusConfigurationOld(testNode);
            // Immediately access children to trigger potential race conditions
            for (int i = 0; i < config.getChildCount(); i++) {
                bh.consume(config.getChild(i).getName());
            }
        } catch (Exception e) {
            bh.consume(e);
        }
    }

    /**
     * Test concurrent construction and access with new implementation
     */
    @Benchmark
    public void concurrentConstructionNew(Blackhole bh) {
        PlexusConfiguration config = new XmlPlexusConfiguration(testNode);
        // Immediately access children to test thread safety
        for (int i = 0; i < config.getChildCount(); i++) {
            bh.consume(config.getChild(i).getName());
        }
    }

    /**
     * Test concurrent attribute access
     */
    @Benchmark
    public void concurrentAttributeAccessOld(Blackhole bh) {
        try {
            String[] attrNames = configOld.getAttributeNames();
            for (String attrName : attrNames) {
                bh.consume(configOld.getAttribute(attrName));
            }
        } catch (Exception e) {
            bh.consume(e);
        }
    }

    @Benchmark
    public void concurrentAttributeAccessNew(Blackhole bh) {
        String[] attrNames = configNew.getAttributeNames();
        for (String attrName : attrNames) {
            bh.consume(configNew.getAttribute(attrName));
        }
    }

    private XmlNode createTestNode() {
        Map<String, String> rootAttrs = Map.of("id", "test-root", "version", "1.0", "type", "benchmark");

        List<XmlNode> children = List.of(
                XmlNode.newBuilder()
                        .name("section1")
                        .attributes(Map.of("name", "section1"))
                        .children(List.of(
                                XmlNode.newInstance("item1", "value1"),
                                XmlNode.newInstance("item2", "value2"),
                                XmlNode.newInstance("item3", "value3")))
                        .build(),
                XmlNode.newBuilder()
                        .name("section2")
                        .attributes(Map.of("name", "section2"))
                        .children(
                                List.of(XmlNode.newInstance("item4", "value4"), XmlNode.newInstance("item5", "value5")))
                        .build(),
                XmlNode.newBuilder()
                        .name("section3")
                        .attributes(Map.of("name", "section3"))
                        .children(List.of(XmlNode.newBuilder()
                                .name("nested")
                                .children(List.of(
                                        XmlNode.newInstance("deep1", "deep-value1"),
                                        XmlNode.newInstance("deep2", "deep-value2")))
                                .build()))
                        .build());

        return XmlNode.newBuilder()
                .name("root")
                .attributes(rootAttrs)
                .children(children)
                .build();
    }
}