Skip to content

Commit 792dd35

Browse files
authored
Merge pull request #377 from Red5/OSRS-14
Fix for racing rtmp clients at startup.
2 parents d398204 + e80de43 commit 792dd35

File tree

3 files changed

+166
-8
lines changed

3 files changed

+166
-8
lines changed

io/src/main/java/org/red5/io/amf/Output.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.lang.reflect.Field;
1212
import java.lang.reflect.Method;
1313
import java.nio.ByteBuffer;
14+
import java.nio.file.Path;
1415
import java.nio.file.Paths;
1516
import java.util.Arrays;
1617
import java.util.Collection;
@@ -20,6 +21,7 @@
2021
import java.util.Set;
2122
import java.util.TimeZone;
2223
import java.util.Vector;
24+
import java.util.concurrent.locks.ReentrantLock;
2325

2426
import org.apache.commons.beanutils.BeanMap;
2527
import org.apache.mina.core.buffer.IoBuffer;
@@ -29,6 +31,7 @@
2931
import org.red5.io.object.RecordSet;
3032
import org.red5.io.object.Serializer;
3133
import org.red5.io.utils.XMLUtils;
34+
import org.red5.resource.RootResolutionException;
3235
import org.slf4j.Logger;
3336
import org.slf4j.LoggerFactory;
3437
import org.w3c.dom.Document;
@@ -51,6 +54,8 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
5154
/** Constant <code>log</code> */
5255
protected static Logger log = LoggerFactory.getLogger(Output.class);
5356

57+
private static ReentrantLock lookupLock = new ReentrantLock();
58+
5459
private static Cache stringCache;
5560

5661
private static Cache serializeCache;
@@ -62,16 +67,37 @@ public class Output extends BaseOutput implements org.red5.io.object.Output {
6267
private static CacheManager cacheManager;
6368

6469
private static CacheManager getCacheManager() {
70+
6571
if (cacheManager == null) {
66-
if (System.getProperty("red5.root") != null) {
67-
try {
68-
cacheManager = new CacheManager(Paths.get(System.getProperty("red5.root"), "conf", "ehcache.xml").toString());
69-
} catch (CacheException e) {
72+
//Lock and load.
73+
lookupLock.lock();//After acquiring the lock, ensure the condition directing this thread to the lock is still true.
74+
try/*to*/ {
75+
CREATE_CACHE_MANAGER: if (cacheManager == null) {
76+
String red5Root = null;
77+
try {
78+
red5Root = Red5Root.get();
79+
} catch (RootResolutionException e) {
80+
log.debug("",e);
81+
}
82+
83+
if (red5Root != null) {
84+
Path conf = Paths.get(red5Root, "conf", "ehcache.xml");
85+
if (conf.toFile().exists()) {
86+
try {
87+
cacheManager = new CacheManager(conf.toString());
88+
break CREATE_CACHE_MANAGER;
89+
} catch (CacheException e) {
90+
log.warn("", e);
91+
}
92+
}
93+
}
7094
cacheManager = constructDefault();
7195
}
72-
} else {
73-
// not a server, maybe running tests?
74-
cacheManager = constructDefault();
96+
} finally {
97+
lookupLock.unlock();
98+
if (cacheManager == null) {
99+
log.info("Failed to create CacheManager.");
100+
}
75101
}
76102
}
77103
return cacheManager;
@@ -680,5 +706,4 @@ public static void destroyCache() {
680706
stringCache = null;
681707
}
682708
}
683-
684709
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.red5.resource;
2+
3+
import java.nio.file.Path;
4+
import java.nio.file.Paths;
5+
import java.security.CodeSource;
6+
import java.security.ProtectionDomain;
7+
import java.util.Optional;
8+
import java.util.concurrent.locks.ReentrantLock;
9+
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
/**
14+
* Utility class to resolve server root
15+
* @author Andy Shaules
16+
*/
17+
public class Red5Root {
18+
19+
protected static Logger log = LoggerFactory.getLogger(Red5Root.class);
20+
21+
private static final String PropertyRed5Root = "red5.root";
22+
23+
private static final String VarRed5Home = "RED5_HOME";
24+
25+
private static final String VarUnixPWD = "PWD";
26+
27+
private static final String PropertyUserDir = "user.dir";
28+
29+
private static String homeDirectory;
30+
31+
private static ReentrantLock lookupLock = new ReentrantLock();
32+
33+
private static ThreadLocal<Throwable> lastError = ThreadLocal.withInitial(()->null);
34+
/**
35+
* Get Root directory of server.
36+
* @return String path or throws exception.
37+
* @throws RootResolutionException if server root cannot be resolved.
38+
*/
39+
public static String get() throws RootResolutionException {
40+
if (homeDirectory == null) {
41+
lookupLock.lock();//After acquiring the lock, ensure the condition directing this thread to the lock is still true.
42+
try {
43+
if (homeDirectory == null) {
44+
homeDirectory = resolve();
45+
}
46+
} finally {
47+
lookupLock.unlock();
48+
}
49+
}
50+
if (homeDirectory == null) {
51+
if(lastError.get()!=null) {
52+
try {
53+
throw new RootResolutionException("Server root path cannot be resolved from system/env properties or code location.",lastError.get());
54+
}finally {
55+
lastError.remove();
56+
}
57+
}else {
58+
throw new RootResolutionException("Server root path cannot be resolved from system/env properties or code location.");
59+
}
60+
}
61+
return homeDirectory;
62+
}
63+
64+
/**
65+
* Returns grandparent directory of this jar, assumed to be in lib folder of 'root/lib/red5-io-version.jar'
66+
* <p>
67+
* Uses:
68+
* <pre>{@code
69+
* sysvar : 'red5.root
70+
* envar : 'RED5_HOME'
71+
* envar : 'PWD'
72+
* sysvar : 'user.dir'}
73+
* </pre>
74+
* As a last resort , it uses {@code Class::getProtectionDomain::getCodeSource::getLocation}
75+
*
76+
* @return server root or null.
77+
*/
78+
private static String resolve() {
79+
/*
80+
We could also look up:
81+
sysvars: 'red5.config_root'='X:\red5\server\conf'
82+
sysvars: 'red5.plugins_root'='X:\red5\server/plugins'
83+
sysvars: 'red5.webapp.root'='X:\red5\server/webapps'
84+
*/
85+
String path = null;
86+
try {
87+
path = System.getProperty(PropertyRed5Root);//Exported system property set by application launch script with value of server's directory. //red5.config_root
88+
if (path == null) {
89+
path = System.getenv(VarRed5Home);//Exported system property set by application launch script with value of server's directory.
90+
if (path == null) {
91+
path = System.getenv(VarUnixPWD);//unix
92+
if (path == null) {
93+
path = System.getProperty(PropertyUserDir);//
94+
if (path == null) {
95+
//Last resort. find this jar location of lib folder, and resolve root directory.
96+
path = Optional.of(Red5Root.class).map(Class::getProtectionDomain).map(ProtectionDomain::getCodeSource).map(CodeSource::getLocation).map(location -> {
97+
try {
98+
return Paths.get(location.toURI());
99+
} catch (Exception e) {
100+
log.warn("",e);
101+
// Wrap URI-specific issues
102+
throw new RuntimeException("Failed to convert URL to URI", e);
103+
}
104+
}).map(Path::getParent).map(Path::getParent).map(Path::toString).orElse(null);
105+
}
106+
}
107+
}
108+
}
109+
} catch (Throwable t) {
110+
//Catch everything possible
111+
lastError.set(t);//Bubble it to the caller gracefully.
112+
log.debug("",t);
113+
}
114+
return path;
115+
}
116+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.red5.resource;
2+
/**
3+
* Exception thrown when server directory cannot be resolved.
4+
* @author Andy Shaules
5+
*/
6+
public class RootResolutionException extends Exception {
7+
8+
private static final long serialVersionUID = 2412009315006073537L;
9+
10+
public RootResolutionException(String message) {
11+
super(message);
12+
}
13+
14+
public RootResolutionException(String message, Throwable cause) {
15+
super(message, cause);
16+
}
17+
}

0 commit comments

Comments
 (0)