mvnd核心流程及源码解析

209 阅读6分钟

项目目录:github.com/apache/mave…

项目介绍

介绍

该项目旨在使用Gradle和Takari已知的技术提供更快的Maven构建。

体系结构概述:

mvnd嵌入了Maven(因此不需要单独安装Maven)。

实际的构建发生在一个长期存在的后台进程中,也称为守护进程。

一个守护进程实例可以为来自mvnd客户端的多个连续请求提供服务。

mvnd客户端是使用GraalVM构建的本机可执行文件。与启动传统JVM相比,它启动得更快,使用的内存更少。

如果没有空闲的守护进程来为构建请求提供服务,则可以并行生成多个守护进程。

此体系结构具有以下优点:

用于运行实际构建的JVM不需要为每个构建重新启动。

持有Maven插件类的类加载器缓存在多个构建中。因此,插件jar只需读取和解析一次。不缓存SNAPSHOT版本的Maven插件。

JVM内部的实时(JIT)编译器生成的本机代码也保留了下来。与Maven相比,JIT编译所花费的时间更少。在重复构建过程中,JIT优化的代码会立即可用。这不仅适用于来自Maven插件和Maven Core的代码,也适用于来自JDK本身的所有代码。

其他功能

mvnd在Maven的基础上带来了以下功能:

  • 默认情况下,mvnd使用多个CPU内核并行构建模块。使用的内核数由公式Math.max(Runtime.getRuntime().available Processors()-1,1)给出。如果源代码树不支持并行构建,请将-T1传递到命令行中,使构建串行化。
  • 改进的控制台输出:我们认为在Maven上并行构建的输出很难遵循。因此,我们实现了一个简化的非滚动视图,在单独的行上显示每个构建线程的状态。

查看守护进程: 可以使用以下命令查看当前运行的守护进程:

mvnd --status

image.png

项目核心流程

  • 执行mvnd.cmdmvnd.sh脚本,选择客户端:

    • 在Windows系统上,会执行mvnd.cmd脚本。
    • 在Unix系统上,会执行mvnd.sh脚本。
    • 这些脚本会首先确定是使用本地的原生客户端还是Java客户端,具体通过环境变量MVND_CLIENT来控制。
    • 如果选择原生客户端,会直接执行mvnd.exe
    • 如果选择Java客户端,会调用Java命令行工具来运行DefaultClient类。
  • 启动客户端

    • 原生客户端(mvnd.exe)和Java客户端(DefaultClient)的行为类似,都会连接到守护进程(Server)并发送构建请求。使用DaemonConnector尝试连接到守护进程。
  • 客户端发送请求

    • 客户端启动后,会读取用户的命令行参数,并创建一个构建请求(BuildRequest)。
    • 通过套接字(Socket)与守护进程(Daemon)通信,将构建请求发送给守护进程。
  • 守护进程接收请求

    • 守护进程的Server类在运行时会接受客户端的连接请求。
    • 创建一个BuildEventListener实例,用于监听构建过程中的各种事件。
  • 执行构建

    • 守护进程解析构建请求,调用Maven CLI进行构建。
    • 通过SmartBuilder来优化构建过程,会并行处理多个构建任务。
    • 构建过程中,会通过BuildEventListener将各种事件和日志信息发送回客户端。
  • 发送构建结果

    • 构建完成后,守护进程将结果(如成功或失败的状态码)发送回客户端。
    • 客户端接收结果,并将其输出到用户的终端。

项目核心代码

围绕上述流程讲解对于代码:

  1. 执行mvnd.cmdmvnd.sh脚本,选择客户端:

image.png 2. DefaultClient 类是 mvnd 项目中客户端的核心部分,负责解析命令行参数、连接守护进程,并处理构建请求。其主要功能包括设置系统属性、启动守护进程、发送构建请求、处理守护进程返回的消息以及日志清理等。

image.png 3. DaemonConnector 通过connect连接到守护进程,查找所有空闲或忙碌的守护进程。如果没有可用的兼容守护进程,则启动一个新的守护进程并返回连接。

Client.java

public interface Client {
    ExecutionResult execute(ClientOutput output, List<String> args) throws InterruptedException;

    default ExecutionResult execute(ClientOutput output, String... args) throws InterruptedException {
        return execute(output, Arrays.asList(args));
    }
}

作用: 定义了客户端执行命令的接口方法。

DaemonConnector.java

  • 作用: 负责连接到已有的守护进程或启动新的守护进程。
public class DaemonConnector {
    private final DaemonParameters parameters;
    private final DaemonRegistry registry;

    public DaemonConnector(DaemonParameters parameters, DaemonRegistry registry) {
        this.parameters = parameters;
        this.registry = registry;
    }

    public DaemonClientConnection connect(ClientOutput output) {
        // 连接或启动守护进程的逻辑
    }
}

DaemonClientConnection.java

作用: 处理与守护进程的通信,发送和接收消息。DaemonClientConnection 类实现了 Closeable 接口,用于与 Daemon 进行通信。它维护了一个消息队列和一个接收线程,用于从 Daemon 接收消息。

public class DaemonClientConnection implements Closeable {
    private final DaemonConnection connection;
    private final DaemonInfo daemon;
    private final boolean newDaemon;
    private boolean hasReceived;
    private final Lock dispatchLock = new ReentrantLock();
    private final BlockingQueue<Message> queue = new ArrayBlockingQueue<>(16);
    private final Thread receiver;
    private final AtomicBoolean running = new AtomicBoolean(true);
    private final AtomicReference<Exception> exception = new AtomicReference<>();
    private final long maxKeepAliveMs;
    private final DaemonParameters parameters;

    public DaemonClientConnection(DaemonConnection connection, DaemonInfo daemon, boolean newDaemon, DaemonParameters parameters) {
        this.connection = connection;
        this.daemon = daemon;
        this.newDaemon = newDaemon;
        this.receiver = new Thread(this::doReceive);
        this.receiver.start();
        this.parameters = parameters;
        this.maxKeepAliveMs = parameters.keepAlive().toMillis() * parameters.maxLostKeepAlive();
    }

    public void dispatch(Message message) throws DaemonException.ConnectException {
        // 发送消息到守护进程
    }

    public List<Message> receive() throws ConnectException, StaleAddressException {
        // 接收来自守护进程的消息
    }

    protected void doReceive() {
        // 后台线程接收消息的实现
    }

    public void close() {
        // 关闭连接
    }
}

Server.java

Server类负责处理守护进程的启动、停止和管理,包括处理客户端请求和调度构建任务。

image.png 创建并运行一个Server实例;

run方法启动守护进程,接受客户端连接,并处理构建请求。

image.png 提供了多个私有方法来处理客户端连接和请求,如accept(接受连接)、client(处理客户端连接)和handle(处理构建请求)。

image.png accept 方法不断地接受客户端连接,并为每个连接创建一个新的线程来处理客户端请求(即调用 client(socket) 方法)。

client类处理客户端连接:

image.png handle类流程如下:

  1. 更新守护进程状态为 Busy

  2. 初始化队列和监听器:

    1. 创建用于发送和接收消息的队列。
    2. 创建 ClientDispatcher 实例作为构建事件监听器。
    3. 创建 DaemonInputStream 实例,用于处理客户端输入数据请求。
  3. 启动一个新的线程,负责从 sendQueue 队列中读取消息并通过连接发送到客户端。

  4. 启动一个新的线程,负责从客户端接收消息并将其放入 recvQueue 队列。

  5. 如果接收到取消构建的消息,则更新状态为 Canceled

  6. 如果守护进程不是在 noDaemon 模式下运行,则将状态更新为 Idle 并触发垃圾回收。

参考