1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2024, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.core.util;
16  
17  import ch.qos.logback.core.Appender;
18  import ch.qos.logback.core.AppenderBase;
19  import ch.qos.logback.core.Context;
20  import ch.qos.logback.core.ContextBase;
21  import ch.qos.logback.core.spi.AppenderAttachableImpl;
22  import org.junit.jupiter.api.Disabled;
23  import org.junit.jupiter.api.Test;
24  
25  import java.util.concurrent.ExecutionException;
26  import java.util.concurrent.ExecutorService;
27  import java.util.concurrent.Executors;
28  
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.List;
32  import java.util.concurrent.Future;
33  import java.util.concurrent.locks.ReentrantLock;
34  
35  import static org.assertj.core.api.Fail.fail;
36  
37  @Disabled
38  public class COWArrayListConcurrencyTest {
39  
40      //private static final int LIST_SIZE = 1_000_000;
41      private static final int LOOP_LEN = 1_0;
42      private static final int RECONFIGURE_DELAY = 1;
43  
44      ReentrantLock reconfigureLock = new ReentrantLock(true);
45      ReentrantLock writeLock = new ReentrantLock(true);
46  
47      private static int THREAD_COUNT = 200; //Runtime.getRuntime().availableProcessors()*200;
48      //private static int THREAD_COUNT = 5000;
49  
50      private final ExecutorService tasksExecutor = Executors.newVirtualThreadPerTaskExecutor();
51      LoopingRunnable[] loopingThreads = new LoopingRunnable[THREAD_COUNT];
52      ReconfiguringThread[] reconfiguringThreads = new ReconfiguringThread[THREAD_COUNT];
53      Future<?>[] futures = new Future[THREAD_COUNT];
54  
55      AppenderAttachableImpl<String> aai = new AppenderAttachableImpl<>();
56      Context context = new ContextBase();
57  
58      void reconfigureWithDelay(AppenderAttachableImpl<String> aai) {
59          try {
60              reconfigureLock.lock();
61              aai.addAppender(makeNewNOPAppender());
62              aai.addAppender(makeNewNOPAppender());
63              delay(RECONFIGURE_DELAY);
64              aai.detachAndStopAllAppenders();
65          } finally {
66              reconfigureLock.unlock();
67          }
68      }
69  
70      private Appender<String> makeNewNOPAppender() {
71          List<Long> longList = new ArrayList<>();
72  //        for (int j = 0; j < LIST_SIZE; j++) {
73  //            longList.add(0L);
74  //        }
75          Appender<String> nopAppenderWithDelay = new NOPAppenderWithDelay<>(longList);
76          nopAppenderWithDelay.setContext(context);
77          nopAppenderWithDelay.start();
78          return nopAppenderWithDelay;
79      }
80  
81      private void delay(int delay) {
82          try {
83              Thread.sleep(delay);
84          } catch (InterruptedException e) {
85              throw new RuntimeException(e);
86          }
87      }
88  
89      @Test
90      void smoke() throws InterruptedException, ExecutionException {
91  
92          for (int i = 0; i < THREAD_COUNT; i++) {
93              System.out.println("i="+i);
94              ReconfiguringThread rt = new ReconfiguringThread(aai);
95              futures[i] = tasksExecutor.submit(rt);
96              reconfiguringThreads[i] = rt;
97          }
98  
99          for (int i = 0; i < THREAD_COUNT; i++) {
100             LoopingRunnable loopingThread = new LoopingRunnable(i, aai);
101             tasksExecutor.submit(loopingThread);
102             loopingThreads[i] = loopingThread;
103         }
104 
105         for (int i = 0; i < THREAD_COUNT; i++) {
106             futures[i].get();
107         }
108 
109         //reconfiguringThread.join();
110         Arrays.stream(loopingThreads).forEach(lt -> lt.active = false);
111 
112     }
113 
114     public class NOPAppenderWithDelay<E> extends AppenderBase<E> {
115 
116         List<Long> longList;
117 
118         NOPAppenderWithDelay(List<Long> longList) {
119             this.longList = new ArrayList<>(longList);
120         }
121 
122         int i = 0;
123 
124         @Override
125         protected void append(E eventObject) {
126             i++;
127             try {
128                 writeLock.lock();
129                 if ((i & 0xF) == 0) {
130                     delay(1);
131                 } else {
132                     //longList.stream().map(x-> x+1);
133                 }
134             } finally {
135                 writeLock.unlock();
136             }
137 
138         }
139 
140     }
141 
142     class ReconfiguringThread extends Thread {
143 
144         AppenderAttachableImpl<String> aai;
145 
146         ReconfiguringThread(AppenderAttachableImpl aai) {
147             this.aai = aai;
148         }
149 
150         public void run() {
151             Thread.yield();
152             for (int i = 0; i < LOOP_LEN; i++) {
153                 reconfigureWithDelay(aai);
154             }
155         }
156 
157 
158     }
159 
160 
161     class LoopingRunnable implements Runnable {
162 
163         int num;
164         AppenderAttachableImpl<String> aai;
165         public boolean active = true;
166 
167         LoopingRunnable(int num, AppenderAttachableImpl aai) {
168            this.num = num;
169            this.aai = aai;
170         }
171 
172         public void run() {
173             System.out.println("LoopingRunnable.run.num="+num);
174             int i = 0;
175             while (active) {
176                 if ((i & 0xFFFFF) == 0) {
177                     long id = Thread.currentThread().threadId();
178                     System.out.println("thread=" + id + " reconfigure=" + i);
179                 }
180                 aai.appendLoopOnAppenders(Integer.toString(i));
181                 i++;
182                 //Thread.yield();
183             }
184         }
185     }
186 
187 
188 }