├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── README.md ├── bin ├── libwrapper-linux-x86-32.so ├── libwrapper-linux-x86-64.so ├── libwrapper-macosx-arm-64.dylib ├── libwrapper-macosx-universal-32.jnilib ├── libwrapper-macosx-universal-64.jnilib ├── psqueue.bat ├── psqueue.sh ├── wrapper-linux-x86-32 ├── wrapper-linux-x86-64 ├── wrapper-macosx-arm-64 ├── wrapper-macosx-universal-32 ├── wrapper-macosx-universal-64 ├── wrapper-windows-x86-32.dll ├── wrapper-windows-x86-32.exe ├── wrapper-windows-x86-64.dll ├── wrapper-windows-x86-64.exe ├── wrapper.conf └── wrapper.jar ├── conf ├── conf.xml └── log4j.xml ├── help-doc └── Test-URL.txt ├── lib ├── EasyFastJson-2.7.2.jar ├── istack-commons-runtime.jar ├── javax.activation-api.jar ├── jaxb-api-2.3.1.jar ├── jaxb-runtime.jar ├── log4j-1.2.17.jar ├── netty-all-4.1.7.Final.jar ├── slf4j-api.jar └── slf4j-log4j.jar └── src ├── com └── leansoft │ └── bigqueue │ ├── BigArrayImpl.java │ ├── BigQueueImpl.java │ ├── FanOutQueueImpl.java │ ├── FanOutQueueImplEx.java │ ├── IBigArray.java │ ├── IBigQueue.java │ ├── IFanOutQueue.java │ ├── cache │ ├── ILRUCache.java │ └── LRUCacheImpl.java │ ├── page │ ├── IMappedPage.java │ ├── IMappedPageFactory.java │ ├── MappedPageFactoryImpl.java │ └── MappedPageImpl.java │ └── utils │ ├── Calculator.java │ ├── FileUtil.java │ └── FolderNameValidator.java └── wjw └── psqueue ├── msg ├── ResAdd.java ├── ResData.java ├── ResList.java ├── ResQueueStatus.java ├── ResSubStatus.java └── ResultCode.java └── server ├── App.java ├── Conf.java ├── HttpRequestHandler.java ├── HttpServerChannelInitializer.java ├── Wrapper.java └── jmx ├── AppMXBean.java ├── JMXTools.java └── annotation ├── Description.java ├── MBean.java └── ManagedOperation.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /classes/ 2 | /db 3 | /log 4 | 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | PSQueueServer 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 14 | org.eclipse.jdt.core.compiler.release=disabled 15 | org.eclipse.jdt.core.compiler.source=1.8 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 支持任意个队列,每个队列任意多个消费者的基于 HTTP GET/POST 协议的轻量级开源简单消息队列服务!协议用纯java实现.可以达到每秒并发40000-50000个请求. 2 | 3 | 队列(Queue)又称先进先出表(First In First Out),即先进入队列的元素,先从队列中取出。加入元素的一头叫“队头”,取出元素的一头叫“队尾”。利用消息队列可以很好地异步处理数据传送和存储,当你频繁地向数据库中插入数据、频繁地向搜索引擎提交数据,就可采取消息队列来异步插入。另外,还可以将较慢的处理逻辑、有并发数量限制的处理逻辑,通过消息队列放在后台处理,例如FLV视频转换、发送手机短信、发送电子邮件等。 4 | 5 | #### PSQueueServer 具有以下特征: 6 | 7 | + [x] 非常简单,基于 HTTP GET/POST 协议。PHP、Java、Perl、Shell、Python、Ruby等支持HTTP协议的编程语言均可调用。 8 | + [x] 完善的JMX管理接口,所有方法全部可以由JMX来管理.为了安全管理方法需要口令! 9 | + [x] 每个队列支持任意多消费者。 10 | + [x] 非常快速,入队列、出队列速度超过40000次/秒。 11 | + [x] 高并发,支持5K以上的并发连接。 12 | + [x] 支持多队列。 13 | + [x] 队列个数无限制,只要系统的磁盘空间够用(缺省单个队列占用磁盘空间是2G)。 14 | + [x] 低内存消耗,海量数据存储,存储几十GB的数据只需不到200MB的物理内存缓冲区。 15 | + [x] 可以实时查看指定队列状态(未读队列数量)。 16 | + [x] 可以查看指定队列,指定消费者的内容,包括未出、已出的队列内容。 17 | + [x] 查看队列内容时,支持多字符集编码。 18 | + [x] 创新设计:消费者可以故意倒回到老的偏移量再次消费数据。这违反了队列的常见约定,但被证明是许多消费者的基本特征。 19 | 20 | ### 注意: 21 | > 22 | 当向队列`add`添加数据而且当队列的`size`超出队列的`capacity`时,是不会报队列满的错误的! 23 | 系统会定时清理(缺省30分钟),把队列的size调整到跟`capacity`一致,这时超出`capacity`的队列尾部的数据会被清理掉!!! -------------------------------------------------------------------------------- /bin/libwrapper-linux-x86-32.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/libwrapper-linux-x86-32.so -------------------------------------------------------------------------------- /bin/libwrapper-linux-x86-64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/libwrapper-linux-x86-64.so -------------------------------------------------------------------------------- /bin/libwrapper-macosx-arm-64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/libwrapper-macosx-arm-64.dylib -------------------------------------------------------------------------------- /bin/libwrapper-macosx-universal-32.jnilib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/libwrapper-macosx-universal-32.jnilib -------------------------------------------------------------------------------- /bin/libwrapper-macosx-universal-64.jnilib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/libwrapper-macosx-universal-64.jnilib -------------------------------------------------------------------------------- /bin/psqueue.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | rem Copyright (c) 1999, 2009 Tanuki Software, Ltd. 5 | rem http://www.tanukisoftware.com 6 | rem All rights reserved. 7 | rem 8 | rem This software is the proprietary information of Tanuki Software. 9 | rem You shall use it only in accordance with the terms of the 10 | rem license agreement you entered into with Tanuki Software. 11 | rem http://wrapper.tanukisoftware.org/doc/english/licenseOverview.html 12 | rem 13 | rem Java Service Wrapper command based script. 14 | rem Optimized for use with version 3.3.5 of the Wrapper. 15 | rem 16 | 17 | if "%OS%"=="Windows_NT" goto nt 18 | echo This script only works with NT-based versions of Windows. 19 | goto :eof 20 | 21 | :nt 22 | rem 23 | rem Find the application home. 24 | rem 25 | rem %~dp0 is location of current script under NT 26 | set _REALPATH=%~dp0 27 | 28 | rem Decide on the wrapper binary. 29 | set _WRAPPER_BASE=wrapper 30 | set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe 31 | if exist "%_WRAPPER_EXE%" goto validate 32 | set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe 33 | if exist "%_WRAPPER_EXE%" goto validate 34 | set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%.exe 35 | if exist "%_WRAPPER_EXE%" goto validate 36 | echo Unable to locate a Wrapper executable using any of the following names: 37 | echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe 38 | echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe 39 | echo %_REALPATH%%_WRAPPER_BASE%.exe 40 | pause 41 | goto :eof 42 | 43 | :validate 44 | rem Find the requested command. 45 | for /F %%v in ('echo %1^|findstr "^console$ ^start$ ^pause$ ^resume$ ^stop$ ^restart$ ^install$ ^remove"') do call :exec set COMMAND=%%v 46 | 47 | if "%COMMAND%" == "" ( 48 | echo Usage: %0 { console : start : pause : resume : stop : restart : install : remove } 49 | pause 50 | goto :eof 51 | ) else ( 52 | shift 53 | ) 54 | 55 | rem 56 | rem Find the wrapper.conf 57 | rem 58 | :conf 59 | set _WRAPPER_CONF="%_REALPATH%\wrapper.conf" 60 | 61 | rem 62 | rem Run the application. 63 | rem At runtime, the current directory will be that of wrapper.exe 64 | rem 65 | call :%COMMAND% 66 | if errorlevel 1 pause 67 | goto :eof 68 | 69 | :console 70 | "%_WRAPPER_EXE%" -c %_WRAPPER_CONF% 71 | goto :eof 72 | 73 | :start 74 | "%_WRAPPER_EXE%" -t %_WRAPPER_CONF% 75 | goto :eof 76 | 77 | :pause 78 | "%_WRAPPER_EXE%" -a %_WRAPPER_CONF% 79 | goto :eof 80 | 81 | :resume 82 | "%_WRAPPER_EXE%" -e %_WRAPPER_CONF% 83 | goto :eof 84 | 85 | :stop 86 | "%_WRAPPER_EXE%" -p %_WRAPPER_CONF% 87 | goto :eof 88 | 89 | :install 90 | "%_WRAPPER_EXE%" -i %_WRAPPER_CONF% 91 | goto :eof 92 | 93 | :remove 94 | "%_WRAPPER_EXE%" -r %_WRAPPER_CONF% 95 | goto :eof 96 | 97 | :restart 98 | call :stop 99 | call :start 100 | goto :eof 101 | 102 | :exec 103 | %* 104 | goto :eof 105 | 106 | -------------------------------------------------------------------------------- /bin/psqueue.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # 4 | # Copyright (c) 1999, 2011 Tanuki Software, Ltd. 5 | # http://www.tanukisoftware.com 6 | # All rights reserved. 7 | # 8 | # This software is the proprietary information of Tanuki Software. 9 | # You shall use it only in accordance with the terms of the 10 | # license agreement you entered into with Tanuki Software. 11 | # http://wrapper.tanukisoftware.com/doc/english/licenseOverview.html 12 | # 13 | # Java Service Wrapper sh script. Suitable for starting and stopping 14 | # wrapped Java applications on UNIX platforms. 15 | # 16 | 17 | #----------------------------------------------------------------------------- 18 | # These settings can be modified to fit the needs of your application 19 | # Optimized for use with version 3.5.9 of the Wrapper. 20 | 21 | if [ "x$JAVA_HOME" = "x" ] 22 | then 23 | export JAVA_HOME=/usr/java/default 24 | fi 25 | export PATH=$JAVA_HOME/bin:$PATH 26 | 27 | # Application 28 | APP_NAME="psqueue" 29 | APP_LONG_NAME="psqueue" 30 | 31 | # Wrapper 32 | WRAPPER_CMD="./wrapper" 33 | WRAPPER_CONF="./wrapper.conf" 34 | 35 | # Priority at which to run the wrapper. See "man nice" for valid priorities. 36 | # nice is only used if a priority is specified. 37 | PRIORITY= 38 | 39 | # Location of the pid file. 40 | PIDDIR="." 41 | 42 | # FIXED_COMMAND tells the script to use a hard coded action rather than 43 | # expecting the first parameter of the command line to be the command. 44 | # By default the command will will be expected to be the first parameter. 45 | #FIXED_COMMAND=console 46 | 47 | # PASS_THROUGH tells the script to pass all arguments through to the JVM 48 | # as is. If FIXED_COMMAND is specified then all arguments will be passed. 49 | # If not set then all arguments starting with the second will be passed. 50 | #PASS_THROUGH=true 51 | 52 | # If uncommented, causes the Wrapper to be shutdown using an anchor file. 53 | # When launched with the 'start' command, it will also ignore all INT and 54 | # TERM signals. 55 | #IGNORE_SIGNALS=true 56 | 57 | # Wrapper will start the JVM asynchronously. Your application may have some 58 | # initialization tasks and it may be desirable to wait a few seconds 59 | # before returning. For example, to delay the invocation of following 60 | # startup scripts. Setting WAIT_AFTER_STARTUP to a positive number will 61 | # cause the start command to delay for the indicated period of time 62 | # (in seconds). 63 | # 64 | WAIT_AFTER_STARTUP=0 65 | 66 | # If set, wait for the wrapper to report that the daemon has started 67 | WAIT_FOR_STARTED_STATUS=true 68 | WAIT_FOR_STARTED_TIMEOUT=120 69 | 70 | # If set, the status, start_msg and stop_msg commands will print out detailed 71 | # state information on the Wrapper and Java processes. 72 | #DETAIL_STATUS=true 73 | 74 | # If set, the 'pause' and 'resume' commands will be enabled. These make it 75 | # possible to pause the JVM or Java application without completely stopping 76 | # the Wrapper. See the wrapper.pausable and wrapper.pausable.stop_jvm 77 | # properties for more information. 78 | #PAUSABLE=true 79 | 80 | # If specified, the Wrapper will be run as the specified user. 81 | # IMPORTANT - Make sure that the user has the required privileges to write 82 | # the PID file and wrapper.log files. Failure to be able to write the log 83 | # file will cause the Wrapper to exit without any way to write out an error 84 | # message. 85 | # NOTE - This will set the user which is used to run the Wrapper as well as 86 | # the JVM and is not useful in situations where a privileged resource or 87 | # port needs to be allocated prior to the user being changed. 88 | #RUN_AS_USER= 89 | 90 | # By default we show a detailed usage block. Uncomment to show brief usage. 91 | #BRIEF_USAGE=true 92 | 93 | # flag for using upstart when installing (rather than init.d rc.d) 94 | USE_UPSTART= 95 | 96 | # When installing on On Mac OSX platforms, the following domain will be used to 97 | # prefix the plist file name. 98 | PLIST_DOMAIN=org.tanukisoftware.wrapper 99 | 100 | # The following two lines are used by the chkconfig command. Change as is 101 | # appropriate for your application. They should remain commented. 102 | # chkconfig: 2345 20 80 103 | # description: Test Wrapper Sample Application 104 | 105 | # Initialization block for the install_initd and remove_initd scripts used by 106 | # SUSE linux distributions. 107 | ### BEGIN INIT INFO 108 | # Provides: testwrapper 109 | # Required-Start: $local_fs $network $syslog 110 | # Should-Start: 111 | # Required-Stop: 112 | # Default-Start: 2 3 4 5 113 | # Default-Stop: 0 1 6 114 | # Short-Description: Test Wrapper Sample Application 115 | # Description: Test Wrapper Sample Application Description 116 | ### END INIT INFO 117 | 118 | # Do not modify anything beyond this point 119 | #----------------------------------------------------------------------------- 120 | 121 | if [ -n "$FIXED_COMMAND" ] 122 | then 123 | COMMAND="$FIXED_COMMAND" 124 | else 125 | COMMAND="$1" 126 | fi 127 | 128 | 129 | # Required for HP-UX Startup 130 | if [ `uname -s` = "HP-UX" -o `uname -s` = "HP-UX64" ] ; then 131 | PATH=$PATH:/usr/bin 132 | fi 133 | 134 | # Get the fully qualified path to the script 135 | case $0 in 136 | /*) 137 | SCRIPT="$0" 138 | ;; 139 | *) 140 | PWD=`pwd` 141 | SCRIPT="$PWD/$0" 142 | ;; 143 | esac 144 | 145 | # Resolve the true real path without any sym links. 146 | CHANGED=true 147 | while [ "X$CHANGED" != "X" ] 148 | do 149 | # Change spaces to ":" so the tokens can be parsed. 150 | SAFESCRIPT=`echo $SCRIPT | sed -e 's; ;:;g'` 151 | # Get the real path to this script, resolving any symbolic links 152 | TOKENS=`echo $SAFESCRIPT | sed -e 's;/; ;g'` 153 | REALPATH= 154 | for C in $TOKENS; do 155 | # Change any ":" in the token back to a space. 156 | C=`echo $C | sed -e 's;:; ;g'` 157 | REALPATH="$REALPATH/$C" 158 | # If REALPATH is a sym link, resolve it. Loop for nested links. 159 | while [ -h "$REALPATH" ] ; do 160 | LS="`ls -ld "$REALPATH"`" 161 | LINK="`expr "$LS" : '.*-> \(.*\)$'`" 162 | if expr "$LINK" : '/.*' > /dev/null; then 163 | # LINK is absolute. 164 | REALPATH="$LINK" 165 | else 166 | # LINK is relative. 167 | REALPATH="`dirname "$REALPATH"`""/$LINK" 168 | fi 169 | done 170 | done 171 | 172 | if [ "$REALPATH" = "$SCRIPT" ] 173 | then 174 | CHANGED="" 175 | else 176 | SCRIPT="$REALPATH" 177 | fi 178 | done 179 | 180 | # Get the location of the script. 181 | REALDIR=`dirname "$REALPATH"` 182 | # Normalize the path 183 | REALDIR=`cd ${REALDIR}; pwd` 184 | 185 | # If the PIDDIR is relative, set its value relative to the full REALPATH to avoid problems if 186 | # the working directory is later changed. 187 | FIRST_CHAR=`echo $PIDDIR | cut -c1,1` 188 | if [ "$FIRST_CHAR" != "/" ] 189 | then 190 | PIDDIR=$REALDIR/$PIDDIR 191 | fi 192 | # Same test for WRAPPER_CMD 193 | FIRST_CHAR=`echo $WRAPPER_CMD | cut -c1,1` 194 | if [ "$FIRST_CHAR" != "/" ] 195 | then 196 | WRAPPER_CMD=$REALDIR/$WRAPPER_CMD 197 | fi 198 | # Same test for WRAPPER_CONF 199 | FIRST_CHAR=`echo $WRAPPER_CONF | cut -c1,1` 200 | if [ "$FIRST_CHAR" != "/" ] 201 | then 202 | WRAPPER_CONF=$REALDIR/$WRAPPER_CONF 203 | fi 204 | 205 | # Process ID 206 | ANCHORFILE="$PIDDIR/$APP_NAME.anchor" 207 | COMMANDFILE="$PIDDIR/$APP_NAME.command" 208 | STATUSFILE="$PIDDIR/$APP_NAME.status" 209 | JAVASTATUSFILE="$PIDDIR/$APP_NAME.java.status" 210 | PIDFILE="$PIDDIR/$APP_NAME.pid" 211 | LOCKDIR="/var/lock/subsys" 212 | LOCKFILE="$LOCKDIR/$APP_NAME" 213 | pid="" 214 | 215 | # Resolve the location of the 'ps' command 216 | PSEXE="/usr/ucb/ps" 217 | if [ ! -x "$PSEXE" ] 218 | then 219 | PSEXE="/usr/bin/ps" 220 | if [ ! -x "$PSEXE" ] 221 | then 222 | PSEXE="/bin/ps" 223 | if [ ! -x "$PSEXE" ] 224 | then 225 | eval echo `gettext 'Unable to locate "ps".'` 226 | eval echo `gettext 'Please report this message along with the location of the command on your system.'` 227 | exit 1 228 | fi 229 | fi 230 | fi 231 | 232 | # Resolve the os 233 | DIST_OS=`uname -s | tr "[A-Z]" "[a-z]" | tr -d ' '` 234 | case "$DIST_OS" in 235 | 'sunos') 236 | DIST_OS="solaris" 237 | ;; 238 | 'hp-ux' | 'hp-ux64') 239 | # HP-UX needs the XPG4 version of ps (for -o args) 240 | DIST_OS="hpux" 241 | UNIX95="" 242 | export UNIX95 243 | ;; 244 | 'darwin') 245 | DIST_OS="macosx" 246 | ;; 247 | 'unix_sv') 248 | DIST_OS="unixware" 249 | ;; 250 | 'os/390') 251 | DIST_OS="zos" 252 | ;; 253 | esac 254 | 255 | # Resolve the architecture 256 | if [ "$DIST_OS" = "macosx" ] 257 | then 258 | OS_VER=`sw_vers | grep 'ProductVersion:' | grep -o '[0-9]*\.[0-9]*\.[0-9]*'` 259 | DIST_ARCH="universal" 260 | if [[ "$OS_VER" < "10.5.0" ]] 261 | then 262 | DIST_BITS="32" 263 | else 264 | DIST_BITS="64" 265 | fi 266 | APP_PLIST_BASE=${PLIST_DOMAIN}.${APP_NAME} 267 | APP_PLIST=${APP_PLIST_BASE}.plist 268 | else 269 | DIST_ARCH= 270 | DIST_ARCH=`uname -p 2>/dev/null | tr "[A-Z]" "[a-z]" | tr -d ' '` 271 | if [ "X$DIST_ARCH" = "X" ] 272 | then 273 | DIST_ARCH="unknown" 274 | fi 275 | if [ "$DIST_ARCH" = "unknown" ] 276 | then 277 | DIST_ARCH=`uname -m 2>/dev/null | tr "[A-Z]" "[a-z]" | tr -d ' '` 278 | fi 279 | case "$DIST_ARCH" in 280 | 'athlon' | 'i386' | 'i486' | 'i586' | 'i686') 281 | DIST_ARCH="x86" 282 | if [ "${DIST_OS}" = "solaris" ] ; then 283 | DIST_BITS=`isainfo -b` 284 | else 285 | DIST_BITS="32" 286 | fi 287 | ;; 288 | 'amd64' | 'x86_64') 289 | DIST_ARCH="x86" 290 | DIST_BITS="64" 291 | ;; 292 | 'ia32') 293 | DIST_ARCH="ia" 294 | DIST_BITS="32" 295 | ;; 296 | 'ia64' | 'ia64n' | 'ia64w') 297 | DIST_ARCH="ia" 298 | DIST_BITS="64" 299 | ;; 300 | 'ip27') 301 | DIST_ARCH="mips" 302 | DIST_BITS="32" 303 | ;; 304 | 'power' | 'powerpc' | 'power_pc' | 'ppc64') 305 | if [ "${DIST_ARCH}" = "ppc64" ] ; then 306 | DIST_BITS="64" 307 | else 308 | DIST_BITS="32" 309 | fi 310 | DIST_ARCH="ppc" 311 | if [ "${DIST_OS}" = "aix" ] ; then 312 | if [ `getconf KERNEL_BITMODE` -eq 64 ]; then 313 | DIST_BITS="64" 314 | else 315 | DIST_BITS="32" 316 | fi 317 | fi 318 | ;; 319 | 'pa_risc' | 'pa-risc') 320 | DIST_ARCH="parisc" 321 | if [ `getconf KERNEL_BITS` -eq 64 ]; then 322 | DIST_BITS="64" 323 | else 324 | DIST_BITS="32" 325 | fi 326 | ;; 327 | 'sun4u' | 'sparcv9' | 'sparc') 328 | DIST_ARCH="sparc" 329 | DIST_BITS=`isainfo -b` 330 | ;; 331 | '9000/800' | '9000/785') 332 | DIST_ARCH="parisc" 333 | if [ `getconf KERNEL_BITS` -eq 64 ]; then 334 | DIST_BITS="64" 335 | else 336 | DIST_BITS="32" 337 | fi 338 | ;; 339 | '2064' | '2066' | '2084' | '2086' | '2094' | '2096' | '2097' | '2098' | '2817') 340 | DIST_ARCH="390" 341 | DIST_BITS="64" 342 | ;; 343 | esac 344 | fi 345 | 346 | # OSX always places Java in the same location so we can reliably set JAVA_HOME 347 | if [ "$DIST_OS" = "macosx" ] 348 | then 349 | if [ -z "$JAVA_HOME" ]; then 350 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 351 | fi 352 | fi 353 | 354 | # Test Echo 355 | ECHOTEST=`echo -n "x"` 356 | if [ "$ECHOTEST" = "x" ] 357 | then 358 | ECHOOPT="-n " 359 | else 360 | ECHOOPT="" 361 | fi 362 | 363 | 364 | gettext() { 365 | "$WRAPPER_CMD" --translate "$1" "$WRAPPER_CONF" 2>/dev/null 366 | if [ $? != 0 ] ; then 367 | echo "$1" 368 | fi 369 | } 370 | 371 | outputFile() { 372 | if [ -f "$1" ] 373 | then 374 | eval echo `gettext ' $1 Found but not executable.'`; 375 | else 376 | echo " $1" 377 | fi 378 | } 379 | 380 | # Decide on the wrapper binary to use. 381 | # If the bits of the OS could be detected, we will try to look for the 382 | # binary with the correct bits value. If it doesn't exist, fall back 383 | # and look for the 32-bit binary. If that doesn't exist either then 384 | # look for the default. 385 | WRAPPER_TEST_CMD="" 386 | if [ -f "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-$DIST_BITS" ] 387 | then 388 | WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-$DIST_BITS" 389 | if [ ! -x "$WRAPPER_TEST_CMD" ] 390 | then 391 | chmod +x "$WRAPPER_TEST_CMD" 2>/dev/null 392 | fi 393 | if [ -x "$WRAPPER_TEST_CMD" ] 394 | then 395 | WRAPPER_CMD="$WRAPPER_TEST_CMD" 396 | else 397 | outputFile "$WRAPPER_TEST_CMD" 398 | WRAPPER_TEST_CMD="" 399 | fi 400 | fi 401 | if [ -f "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32" -a -z "$WRAPPER_TEST_CMD" ] 402 | then 403 | WRAPPER_TEST_CMD="$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32" 404 | if [ ! -x "$WRAPPER_TEST_CMD" ] 405 | then 406 | chmod +x "$WRAPPER_TEST_CMD" 2>/dev/null 407 | fi 408 | if [ -x "$WRAPPER_TEST_CMD" ] 409 | then 410 | WRAPPER_CMD="$WRAPPER_TEST_CMD" 411 | else 412 | outputFile "$WRAPPER_TEST_CMD" 413 | WRAPPER_TEST_CMD="" 414 | fi 415 | fi 416 | if [ -f "$WRAPPER_CMD" -a -z "$WRAPPER_TEST_CMD" ] 417 | then 418 | WRAPPER_TEST_CMD="$WRAPPER_CMD" 419 | if [ ! -x "$WRAPPER_TEST_CMD" ] 420 | then 421 | chmod +x "$WRAPPER_TEST_CMD" 2>/dev/null 422 | fi 423 | if [ -x "$WRAPPER_TEST_CMD" ] 424 | then 425 | WRAPPER_CMD="$WRAPPER_TEST_CMD" 426 | else 427 | outputFile "$WRAPPER_TEST_CMD" 428 | WRAPPER_TEST_CMD="" 429 | fi 430 | fi 431 | if [ -z "$WRAPPER_TEST_CMD" ] 432 | then 433 | eval echo `gettext 'Unable to locate any of the following binaries:'` 434 | outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-$DIST_BITS" 435 | if [ ! "$DIST_BITS" = "32" ] 436 | then 437 | outputFile "$WRAPPER_CMD-$DIST_OS-$DIST_ARCH-32" 438 | fi 439 | outputFile "$WRAPPER_CMD" 440 | 441 | exit 1 442 | fi 443 | 444 | 445 | # Build the nice clause 446 | if [ "X$PRIORITY" = "X" ] 447 | then 448 | CMDNICE="" 449 | else 450 | CMDNICE="nice -$PRIORITY" 451 | fi 452 | 453 | # Build the anchor file clause. 454 | if [ "X$IGNORE_SIGNALS" = "X" ] 455 | then 456 | ANCHORPROP= 457 | IGNOREPROP= 458 | else 459 | ANCHORPROP=wrapper.anchorfile=\"$ANCHORFILE\" 460 | IGNOREPROP=wrapper.ignore_signals=TRUE 461 | fi 462 | 463 | # Build the status file clause. 464 | if [ "X$DETAIL_STATUS$WAIT_FOR_STARTED_STATUS" = "X" ] 465 | then 466 | STATUSPROP= 467 | else 468 | STATUSPROP="wrapper.statusfile=\"$STATUSFILE\" wrapper.java.statusfile=\"$JAVASTATUSFILE\"" 469 | fi 470 | 471 | # Build the command file clause. 472 | if [ -n "$PAUSABLE" ] 473 | then 474 | COMMANDPROP="wrapper.commandfile=\"$COMMANDFILE\" wrapper.pausable=TRUE" 475 | else 476 | COMMANDPROP= 477 | fi 478 | 479 | if [ ! -n "$WAIT_FOR_STARTED_STATUS" ] 480 | then 481 | WAIT_FOR_STARTED_STATUS=true 482 | fi 483 | 484 | if [ $WAIT_FOR_STARTED_STATUS = true ] ; then 485 | DETAIL_STATUS=true 486 | fi 487 | 488 | 489 | # Build the lock file clause. Only create a lock file if the lock directory exists on this platform. 490 | LOCKPROP= 491 | if [ -d $LOCKDIR ] 492 | then 493 | if [ -w $LOCKDIR ] 494 | then 495 | LOCKPROP=wrapper.lockfile=\"$LOCKFILE\" 496 | fi 497 | fi 498 | 499 | prepAdditionalParams() { 500 | ADDITIONAL_PARA="" 501 | if [ -n "$PASS_THROUGH" ] ; then 502 | ADDITIONAL_PARA="--" 503 | fi 504 | while [ -n "$1" ] ; do 505 | ADDITIONAL_PARA="$ADDITIONAL_PARA \"$1\"" 506 | shift 507 | done 508 | } 509 | 510 | checkUser() { 511 | # $1 touchLock flag 512 | # $2.. [command] args 513 | 514 | # Check the configured user. If necessary rerun this script as the desired user. 515 | if [ "X$RUN_AS_USER" != "X" ] 516 | then 517 | # Resolve the location of the 'id' command 518 | IDEXE="/usr/xpg4/bin/id" 519 | if [ ! -x "$IDEXE" ] 520 | then 521 | IDEXE="/usr/bin/id" 522 | if [ ! -x "$IDEXE" ] 523 | then 524 | eval echo `gettext 'Unable to locate "id".'` 525 | eval echo `gettext 'Please report this message along with the location of the command on your system.'` 526 | exit 1 527 | fi 528 | fi 529 | 530 | if [ "`$IDEXE -u -n`" = "$RUN_AS_USER" ] 531 | then 532 | # Already running as the configured user. Avoid password prompts by not calling su. 533 | RUN_AS_USER="" 534 | fi 535 | fi 536 | if [ "X$RUN_AS_USER" != "X" ] 537 | then 538 | # If LOCKPROP and $RUN_AS_USER are defined then the new user will most likely not be 539 | # able to create the lock file. The Wrapper will be able to update this file once it 540 | # is created but will not be able to delete it on shutdown. If $1 is set then 541 | # the lock file should be created for the current command 542 | if [ "X$LOCKPROP" != "X" ] 543 | then 544 | if [ "X$1" != "X" ] 545 | then 546 | # Resolve the primary group 547 | RUN_AS_GROUP=`groups $RUN_AS_USER | awk '{print $3}' | tail -1` 548 | if [ "X$RUN_AS_GROUP" = "X" ] 549 | then 550 | RUN_AS_GROUP=$RUN_AS_USER 551 | fi 552 | touch $LOCKFILE 553 | chown $RUN_AS_USER:$RUN_AS_GROUP $LOCKFILE 554 | fi 555 | fi 556 | 557 | # Still want to change users, recurse. This means that the user will only be 558 | # prompted for a password once. Variables shifted by 1 559 | shift 560 | 561 | # Wrap the parameters so they can be passed. 562 | ADDITIONAL_PARA="" 563 | while [ -n "$1" ] ; do 564 | ADDITIONAL_PARA="$ADDITIONAL_PARA \"$1\"" 565 | shift 566 | done 567 | 568 | # Use "runuser" if this exists. runuser should be used on RedHat in preference to su. 569 | # 570 | if test -f "/sbin/runuser" 571 | then 572 | /sbin/runuser - $RUN_AS_USER -c "\"$REALPATH\" $ADDITIONAL_PARA" 573 | else 574 | su - $RUN_AS_USER -c "\"$REALPATH\" $ADDITIONAL_PARA" 575 | fi 576 | RUN_AS_USER_EXITCODE=$? 577 | # Now that we are the original user again, we may need to clean up the lock file. 578 | if [ "X$LOCKPROP" != "X" ] 579 | then 580 | getpid 581 | if [ "X$pid" = "X" ] 582 | then 583 | # Wrapper is not running so make sure the lock file is deleted. 584 | if [ -f "$LOCKFILE" ] 585 | then 586 | rm "$LOCKFILE" 587 | fi 588 | fi 589 | fi 590 | 591 | exit $RUN_AS_USER_EXITCODE 592 | fi 593 | } 594 | 595 | getpid() { 596 | pid="" 597 | if [ -f "$PIDFILE" ] 598 | then 599 | if [ -r "$PIDFILE" ] 600 | then 601 | pid=`cat "$PIDFILE"` 602 | if [ "X$pid" != "X" ] 603 | then 604 | # It is possible that 'a' process with the pid exists but that it is not the 605 | # correct process. This can happen in a number of cases, but the most 606 | # common is during system startup after an unclean shutdown. 607 | # The ps statement below looks for the specific wrapper command running as 608 | # the pid. If it is not found then the pid file is considered to be stale. 609 | case "$DIST_OS" in 610 | 'freebsd') 611 | pidtest=`$PSEXE -p $pid -o args | tail -1` 612 | if [ "X$pidtest" = "XCOMMAND" ] 613 | then 614 | pidtest="" 615 | fi 616 | ;; 617 | 'macosx') 618 | pidtest=`$PSEXE -ww -p $pid -o command | grep -F "$WRAPPER_CMD" | tail -1` 619 | ;; 620 | 'solaris') 621 | if [ -f "/usr/bin/pargs" ] 622 | then 623 | pidtest=`pargs $pid | fgrep "$WRAPPER_CMD" | tail -1` 624 | else 625 | case "$PSEXE" in 626 | '/usr/ucb/ps') 627 | pidtest=`$PSEXE -auxww $pid | fgrep "$WRAPPER_CMD" | tail -1` 628 | ;; 629 | '/usr/bin/ps') 630 | TRUNCATED_CMD=`$PSEXE -o comm -p $pid | tail -1` 631 | COUNT=`echo $TRUNCATED_CMD | wc -m` 632 | COUNT=`echo ${COUNT}` 633 | COUNT=`expr $COUNT - 1` 634 | TRUNCATED_CMD=`echo $WRAPPER_CMD | cut -c1-$COUNT` 635 | pidtest=`$PSEXE -o comm -p $pid | fgrep "$TRUNCATED_CMD" | tail -1` 636 | ;; 637 | '/bin/ps') 638 | TRUNCATED_CMD=`$PSEXE -o comm -p $pid | tail -1` 639 | COUNT=`echo $TRUNCATED_CMD | wc -m` 640 | COUNT=`echo ${COUNT}` 641 | COUNT=`expr $COUNT - 1` 642 | TRUNCATED_CMD=`echo $WRAPPER_CMD | cut -c1-$COUNT` 643 | pidtest=`$PSEXE -o comm -p $pid | fgrep "$TRUNCATED_CMD" | tail -1` 644 | ;; 645 | *) 646 | echo "Unsupported ps command $PSEXE" 647 | exit 1 648 | ;; 649 | esac 650 | fi 651 | ;; 652 | 'hpux') 653 | pidtest=`$PSEXE -p $pid -x -o args | grep -F "$WRAPPER_CMD" | tail -1` 654 | ;; 655 | *) 656 | pidtest=`$PSEXE -p $pid -o args | grep -F "$WRAPPER_CMD" | tail -1` 657 | ;; 658 | esac 659 | 660 | if [ "X$pidtest" = "X" ] 661 | then 662 | # This is a stale pid file. 663 | rm -f "$PIDFILE" 664 | eval echo `gettext 'Removed stale pid file: $PIDFILE'` 665 | pid="" 666 | fi 667 | fi 668 | else 669 | eval echo `gettext 'Cannot read $PIDFILE.'` 670 | exit 1 671 | fi 672 | fi 673 | } 674 | 675 | getstatus() { 676 | STATUS= 677 | if [ -f "$STATUSFILE" ] 678 | then 679 | if [ -r "$STATUSFILE" ] 680 | then 681 | STATUS=`cat "$STATUSFILE"` 682 | fi 683 | fi 684 | if [ "X$STATUS" = "X" ] 685 | then 686 | STATUS="Unknown" 687 | fi 688 | 689 | JAVASTATUS= 690 | if [ -f "$JAVASTATUSFILE" ] 691 | then 692 | if [ -r "$JAVASTATUSFILE" ] 693 | then 694 | JAVASTATUS=`cat "$JAVASTATUSFILE"` 695 | fi 696 | fi 697 | if [ "X$JAVASTATUS" = "X" ] 698 | then 699 | JAVASTATUS="Unknown" 700 | fi 701 | } 702 | 703 | testpid() { 704 | case "$DIST_OS" in 705 | 'solaris') 706 | case "$PSEXE" in 707 | '/usr/ucb/ps') 708 | pid=`$PSEXE $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1` 709 | ;; 710 | '/usr/bin/ps') 711 | pid=`$PSEXE -p $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1` 712 | ;; 713 | '/bin/ps') 714 | pid=`$PSEXE -p $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1` 715 | ;; 716 | *) 717 | echo "Unsupported ps command $PSEXE" 718 | exit 1 719 | ;; 720 | esac 721 | ;; 722 | *) 723 | pid=`$PSEXE -p $pid | grep $pid | grep -v grep | awk '{print $1}' | tail -1` 2>/dev/null 724 | ;; 725 | esac 726 | if [ "X$pid" = "X" ] 727 | then 728 | # Process is gone so remove the pid file. 729 | rm -f "$PIDFILE" 730 | pid="" 731 | fi 732 | } 733 | 734 | launchdtrap() { 735 | stopit 736 | exit 737 | } 738 | 739 | waitforwrapperstop() { 740 | getpid 741 | while [ "X$pid" != "X" ] ; do 742 | sleep 1 743 | getpid 744 | done 745 | } 746 | 747 | launchdinternal() { 748 | getpid 749 | trap launchdtrap TERM 750 | if [ "X$pid" = "X" ] 751 | then 752 | prepAdditionalParams "$@" 753 | 754 | # The string passed to eval must handles spaces in paths correctly. 755 | COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" wrapper.daemonize=TRUE $ANCHORPROP $IGNOREPROP $STATUSPROP $COMMANDPROP $LOCKPROP $ADDITIONAL_PARA" 756 | eval $COMMAND_LINE 757 | else 758 | eval echo `gettext '$APP_LONG_NAME is already running.'` 759 | exit 1 760 | fi 761 | # launchd expects that this script stay up and running so we need to do our own monitoring of the Wrapper process. 762 | if [ $WAIT_FOR_STARTED_STATUS = true ] 763 | then 764 | waitforwrapperstop 765 | fi 766 | } 767 | 768 | console() { 769 | eval echo `gettext 'Running $APP_LONG_NAME...'` 770 | getpid 771 | if [ "X$pid" = "X" ] 772 | then 773 | trap '' 3 774 | 775 | prepAdditionalParams "$@" 776 | 777 | # The string passed to eval must handles spaces in paths correctly. 778 | COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" $ANCHORPROP $STATUSPROP $COMMANDPROP $LOCKPROP $ADDITIONAL_PARA" 779 | eval $COMMAND_LINE 780 | else 781 | eval echo `gettext '$APP_LONG_NAME is already running.'` 782 | exit 1 783 | fi 784 | } 785 | 786 | waitforjavastartup() { 787 | getstatus 788 | eval echo $ECHOOPT `gettext 'Waiting for $APP_LONG_NAME...'` 789 | 790 | # Wait until the timeout or we have something besides Unknown. 791 | counter=15 792 | while [ "$JAVASTATUS" = "Unknown" -a $counter -gt 0 -a -n "$JAVASTATUS" ] ; do 793 | echo $ECHOOPT"." 794 | sleep 1 795 | getstatus 796 | counter=`expr $counter - 1` 797 | done 798 | 799 | if [ -n "$WAIT_FOR_STARTED_TIMEOUT" ] ; then 800 | counter=$WAIT_FOR_STARTED_TIMEOUT 801 | else 802 | counter=120 803 | fi 804 | while [ "$JAVASTATUS" != "STARTED" -a "$JAVASTATUS" != "Unknown" -a $counter -gt 0 -a -n "$JAVASTATUS" ] ; do 805 | echo $ECHOOPT"." 806 | sleep 1 807 | getstatus 808 | counter=`expr $counter - 1` 809 | done 810 | if [ "X$ECHOOPT" != "X" ] ; then 811 | echo "" 812 | fi 813 | } 814 | 815 | startwait() { 816 | if [ $WAIT_FOR_STARTED_STATUS = true ] 817 | then 818 | waitforjavastartup 819 | fi 820 | # Sleep for a few seconds to allow for intialization if required 821 | # then test to make sure we're still running. 822 | # 823 | i=0 824 | while [ $i -lt $WAIT_AFTER_STARTUP ] 825 | do 826 | sleep 1 827 | echo $ECHOOPT"." 828 | i=`expr $i + 1` 829 | done 830 | if [ $WAIT_AFTER_STARTUP -gt 0 -o $WAIT_FOR_STARTED_STATUS = true ] 831 | then 832 | getpid 833 | if [ "X$pid" = "X" ] 834 | then 835 | eval echo `gettext ' WARNING: $APP_LONG_NAME may have failed to start.'` 836 | exit 1 837 | else 838 | eval echo `gettext ' running: PID:$pid'` 839 | fi 840 | else 841 | echo "" 842 | fi 843 | } 844 | 845 | macosxstart() { 846 | # The daemon has been installed. 847 | eval echo `gettext 'Starting $APP_LONG_NAME. Detected Mac OSX and installed launchd daemon.'` 848 | if [ `id | sed 's/^uid=//;s/(.*$//'` != "0" ] ; then 849 | eval echo `gettext 'Must be root to perform this action.'` 850 | exit 1 851 | fi 852 | 853 | getpid 854 | if [ "X$pid" != "X" ] ; then 855 | eval echo `gettext '$APP_LONG_NAME is already running.'` 856 | exit 1 857 | fi 858 | 859 | # If the daemon was just installed, it may not be loaded. 860 | LOADED_PLIST=`launchctl list | grep ${APP_PLIST_BASE}` 861 | if [ "X${LOADED_PLIST}" = "X" ] ; then 862 | launchctl load /Library/LaunchDaemons/${APP_PLIST} 863 | fi 864 | # If launchd is set to run the daemon already at Load, we don't need to call start 865 | getpid 866 | if [ "X$pid" == "X" ] ; then 867 | launchctl start ${APP_PLIST_BASE} 868 | fi 869 | 870 | startwait 871 | } 872 | 873 | upstartstart() { 874 | # The daemon has been installed. 875 | eval echo `gettext 'Starting $APP_LONG_NAME. Detected Linux and installed upstart.'` 876 | if [ `id | sed 's/^uid=//;s/(.*$//'` != "0" ] ; then 877 | eval echo `gettext 'Must be root to perform this action.'` 878 | exit 1 879 | fi 880 | 881 | getpid 882 | if [ "X$pid" != "X" ] ; then 883 | eval echo `gettext '$APP_LONG_NAME is already running.'` 884 | exit 1 885 | fi 886 | 887 | /sbin/start ${APP_NAME} 888 | 889 | startwait 890 | } 891 | 892 | start() { 893 | eval echo `gettext 'Starting $APP_LONG_NAME...'` 894 | getpid 895 | if [ "X$pid" = "X" ] 896 | then 897 | prepAdditionalParams "$@" 898 | 899 | # The string passed to eval must handles spaces in paths correctly. 900 | COMMAND_LINE="$CMDNICE \"$WRAPPER_CMD\" \"$WRAPPER_CONF\" wrapper.syslog.ident=\"$APP_NAME\" wrapper.pidfile=\"$PIDFILE\" wrapper.name=\"$APP_NAME\" wrapper.displayname=\"$APP_LONG_NAME\" wrapper.daemonize=TRUE $ANCHORPROP $IGNOREPROP $STATUSPROP $COMMANDPROP $LOCKPROP $ADDITIONAL_PARA" 901 | eval $COMMAND_LINE 902 | else 903 | eval echo `gettext '$APP_LONG_NAME is already running.'` 904 | exit 1 905 | fi 906 | 907 | startwait 908 | } 909 | 910 | stopit() { 911 | # $1 exit if down flag 912 | 913 | eval echo `gettext 'Stopping $APP_LONG_NAME...'` 914 | getpid 915 | if [ "X$pid" = "X" ] 916 | then 917 | eval echo `gettext '$APP_LONG_NAME was not running.'` 918 | if [ "X$1" = "X1" ] 919 | then 920 | exit 1 921 | fi 922 | else 923 | if [ "X$IGNORE_SIGNALS" = "X" ] 924 | then 925 | # Running so try to stop it. 926 | kill $pid 927 | if [ $? -ne 0 ] 928 | then 929 | # An explanation for the failure should have been given 930 | eval echo `gettext 'Unable to stop $APP_LONG_NAME.'` 931 | exit 1 932 | fi 933 | else 934 | rm -f "$ANCHORFILE" 935 | if [ -f "$ANCHORFILE" ] 936 | then 937 | # An explanation for the failure should have been given 938 | eval echo `gettext 'Unable to stop $APP_LONG_NAME.'` 939 | exit 1 940 | fi 941 | fi 942 | 943 | # We can not predict how long it will take for the wrapper to 944 | # actually stop as it depends on settings in wrapper.conf. 945 | # Loop until it does. 946 | savepid=$pid 947 | CNT=0 948 | TOTCNT=0 949 | while [ "X$pid" != "X" ] 950 | do 951 | # Show a waiting message every 5 seconds. 952 | if [ "$CNT" -lt "5" ] 953 | then 954 | CNT=`expr $CNT + 1` 955 | else 956 | eval echo `gettext 'Waiting for $APP_LONG_NAME to exit...'` 957 | CNT=0 958 | fi 959 | TOTCNT=`expr $TOTCNT + 1` 960 | 961 | sleep 1 962 | 963 | testpid 964 | done 965 | 966 | pid=$savepid 967 | testpid 968 | if [ "X$pid" != "X" ] 969 | then 970 | eval echo `gettext 'Failed to stop $APP_LONG_NAME.'` 971 | exit 1 972 | else 973 | eval echo `gettext 'Stopped $APP_LONG_NAME.'` 974 | fi 975 | fi 976 | } 977 | 978 | pause() { 979 | eval echo `gettext 'Pausing $APP_LONG_NAME.'` 980 | } 981 | 982 | resume() { 983 | eval echo `gettext 'Resuming $APP_LONG_NAME.'` 984 | } 985 | 986 | status() { 987 | getpid 988 | if [ "X$pid" = "X" ] 989 | then 990 | eval echo `gettext '$APP_LONG_NAME is not running.'` 991 | exit 1 992 | else 993 | if [ "X$DETAIL_STATUS" = "X" ] 994 | then 995 | eval echo `gettext '$APP_LONG_NAME is running: PID:$pid'` 996 | else 997 | getstatus 998 | eval echo `gettext '$APP_LONG_NAME is running: PID:$pid, Wrapper:$STATUS, Java:$JAVASTATUS'` 999 | fi 1000 | exit 0 1001 | fi 1002 | } 1003 | 1004 | installUpstart() { 1005 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon using upstart..'` 1006 | if [ -f "${APP_NAME}.conf" ] ; then 1007 | eval echo `gettext ' a custom upstart conf file ${APP_NAME}.conf found'` 1008 | cp "${REALDIR}/${APP_NAME}.install" "/etc/init/${APP_NAME}.conf" 1009 | else 1010 | eval echo `gettext ' creating default upstart conf file..'` 1011 | echo "# ${APP_NAME} - ${APP_LONG_NAME}" > "/etc/init/${APP_NAME}.conf" 1012 | echo "description \"${APP_LONG_NAME}\"" >> "/etc/init/${APP_NAME}.conf" 1013 | echo "author \"Tanuki Software Ltd. \"" >> "/etc/init/${APP_NAME}.conf" 1014 | echo "start on runlevel [2345]" >> "/etc/init/${APP_NAME}.conf" 1015 | echo "stop on runlevel [!2345]" >> "/etc/init/${APP_NAME}.conf" 1016 | echo "env LANG=${LANG}" >> "/etc/init/${APP_NAME}.conf" 1017 | echo "exec \"${REALPATH}\" launchdinternal" >> "/etc/init/${APP_NAME}.conf" 1018 | fi 1019 | } 1020 | 1021 | installdaemon() { 1022 | if [ `id | sed 's/^uid=//;s/(.*$//'` != "0" ] ; then 1023 | eval echo `gettext 'Must be root to perform this action.'` 1024 | exit 1 1025 | else 1026 | APP_NAME_LOWER=`echo "$APP_NAME" | tr "[A-Z]" "[a-z]"` 1027 | if [ "$DIST_OS" = "solaris" ] ; then 1028 | eval echo `gettext 'Detected Solaris:'` 1029 | if [ -f /etc/init.d/$APP_NAME ] ; then 1030 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1031 | exit 1 1032 | else 1033 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1034 | ln -s "$REALPATH" "/etc/init.d/$APP_NAME" 1035 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc3.d/K20$APP_NAME_LOWER" 1036 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc3.d/S20$APP_NAME_LOWER" 1037 | fi 1038 | elif [ "$DIST_OS" = "linux" ] ; then 1039 | if [ -f /etc/redhat-release -o -f /etc/redhat_version -o -f /etc/fedora-release ] ; then 1040 | eval echo `gettext 'Detected RHEL or Fedora:'` 1041 | if [ -f "/etc/init.d/$APP_NAME" -o -f "/etc/init/${APP_NAME}.conf" ] ; then 1042 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1043 | exit 1 1044 | else 1045 | if [ -n "$USE_UPSTART" -a -d "/etc/init" ] ; then 1046 | installUpstart 1047 | else 1048 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1049 | ln -s "$REALPATH" "/etc/init.d/$APP_NAME" 1050 | /sbin/chkconfig --add "$APP_NAME" 1051 | /sbin/chkconfig "$APP_NAME" on 1052 | fi 1053 | fi 1054 | elif [ -f /etc/SuSE-release ] ; then 1055 | eval echo `gettext 'Detected SuSE or SLES:'` 1056 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1057 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1058 | exit 1 1059 | else 1060 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1061 | ln -s "$REALPATH" "/etc/init.d/$APP_NAME" 1062 | insserv "/etc/init.d/$APP_NAME" 1063 | fi 1064 | elif [ -f /etc/lsb-release ] ; then 1065 | eval echo `gettext 'Detected Ubuntu:'` 1066 | if [ -f "/etc/init.d/$APP_NAME" -o -f "/etc/init/${APP_NAME}.conf" ] ; then 1067 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1068 | exit 1 1069 | else 1070 | if [ -n "$USE_UPSTART" -a -d "/etc/init" ] ; then 1071 | installUpstart 1072 | else 1073 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon using init.d..'` 1074 | ln -s "$REALPATH" "/etc/init.d/$APP_NAME" 1075 | update-rc.d "$APP_NAME" defaults 1076 | fi 1077 | fi 1078 | else 1079 | eval echo `gettext 'Detected Linux:'` 1080 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1081 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1082 | exit 1 1083 | else 1084 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1085 | ln -s "$REALPATH" /etc/init.d/$APP_NAME 1086 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc3.d/K20$APP_NAME_LOWER" 1087 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc3.d/S20$APP_NAME_LOWER" 1088 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc5.d/S20$APP_NAME_LOWER" 1089 | ln -s "/etc/init.d/$APP_NAME" "/etc/rc5.d/K20$APP_NAME_LOWER" 1090 | fi 1091 | fi 1092 | elif [ "$DIST_OS" = "hpux" ] ; then 1093 | eval echo `gettext 'Detected HP-UX:'` 1094 | if [ -f "/sbin/init.d/$APP_NAME" ] ; then 1095 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1096 | exit 1 1097 | else 1098 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1099 | ln -s "$REALPATH" "/sbin/init.d/$APP_NAME" 1100 | ln -s "/sbin/init.d/$APP_NAME" "/sbin/rc3.d/K20$APP_NAME_LOWER" 1101 | ln -s "/sbin/init.d/$APP_NAME" "/sbin/rc3.d/S20$APP_NAME_LOWER" 1102 | fi 1103 | elif [ "$DIST_OS" = "aix" ] ; then 1104 | eval echo `gettext 'Detected AIX:'` 1105 | if [ -f "/etc/rc.d/init.d/$APP_NAME" ] ; then 1106 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed as rc.d script.'` 1107 | exit 1 1108 | elif [ -n "`/usr/sbin/lsitab $APP_NAME`" -a -n "`/usr/bin/lssrc -S -s $APP_NAME`" ] ; then 1109 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed as SRC service.'` 1110 | exit 1 1111 | else 1112 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1113 | if [ -n "`/usr/sbin/lsitab install_assist`" ] ; then 1114 | eval echo `gettext ' The task /usr/sbin/install_assist was found in the inittab, this might cause problems for all subsequent tasks to launch at this process is known to block the init task. Please make sure this task is not needed anymore and remove/deactivate it.'` 1115 | fi 1116 | /usr/bin/mkssys -s "$APP_NAME" -p "$REALPATH" -a "launchdinternal" -u 0 -f 9 -n 15 -S 1117 | /usr/sbin/mkitab "$APP_NAME":2:once:"/usr/bin/startsrc -s \"${APP_NAME}\" >/dev/console 2>&1" 1118 | 1119 | fi 1120 | elif [ "$DIST_OS" = "freebsd" ] ; then 1121 | eval echo `gettext 'Detected FreeBSD:'` 1122 | if [ -f "/etc/rc.d/$APP_NAME" ] ; then 1123 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1124 | exit 1 1125 | else 1126 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1127 | sed -i .bak "/${APP_NAME}_enable=\"YES\"/d" /etc/rc.conf 1128 | if [ -f "${REALDIR}/${APP_NAME}.install" ] ; then 1129 | ln -s "${REALDIR}/${APP_NAME}.install" "/etc/rc.d/$APP_NAME" 1130 | else 1131 | echo '#!/bin/sh' > "/etc/rc.d/$APP_NAME" 1132 | echo "#" >> "/etc/rc.d/$APP_NAME" 1133 | echo "# PROVIDE: $APP_NAME" >> "/etc/rc.d/$APP_NAME" 1134 | echo "# REQUIRE: NETWORKING" >> "/etc/rc.d/$APP_NAME" 1135 | echo "# KEYWORD: shutdown" >> "/etc/rc.d/$APP_NAME" 1136 | echo ". /etc/rc.subr" >> "/etc/rc.d/$APP_NAME" 1137 | echo "name=\"$APP_NAME\"" >> "/etc/rc.d/$APP_NAME" 1138 | echo "rcvar=\`set_rcvar\`" >> "/etc/rc.d/$APP_NAME" 1139 | echo "command=\"${REALDIR}/${APP_NAME}\"" >> "/etc/rc.d/$APP_NAME" 1140 | echo 'start_cmd="${name}_start"' >> "/etc/rc.d/$APP_NAME" 1141 | echo 'load_rc_config $name' >> "/etc/rc.d/$APP_NAME" 1142 | echo 'status_cmd="${name}_status"' >> "/etc/rc.d/$APP_NAME" 1143 | echo 'stop_cmd="${name}_stop"' >> "/etc/rc.d/$APP_NAME" 1144 | echo "${APP_NAME}_status() {" >> "/etc/rc.d/$APP_NAME" 1145 | echo '${command} status' >> "/etc/rc.d/$APP_NAME" 1146 | echo '}' >> "/etc/rc.d/$APP_NAME" 1147 | echo "${APP_NAME}_stop() {" >> "/etc/rc.d/$APP_NAME" 1148 | echo '${command} stop' >> "/etc/rc.d/$APP_NAME" 1149 | echo '}' >> "/etc/rc.d/$APP_NAME" 1150 | echo "${APP_NAME}_start() {" >> "/etc/rc.d/$APP_NAME" 1151 | echo '${command} start' >> "/etc/rc.d/$APP_NAME" 1152 | echo '}' >> "/etc/rc.d/$APP_NAME" 1153 | echo 'run_rc_command "$1"' >> "/etc/rc.d/$APP_NAME" 1154 | fi 1155 | echo "${APP_NAME}_enable=\"YES\"" >> /etc/rc.conf 1156 | chmod 555 "/etc/rc.d/$APP_NAME" 1157 | fi 1158 | elif [ "$DIST_OS" = "macosx" ] ; then 1159 | eval echo `gettext 'Detected Mac OSX:'` 1160 | if [ -f "/Library/LaunchDaemons/${APP_PLIST}" ] ; then 1161 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1162 | exit 1 1163 | else 1164 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1165 | if [ -f "${REALDIR}/${APP_PLIST}" ] ; then 1166 | ln -s "${REALDIR}/${APP_PLIST}" "/Library/LaunchDaemons/${APP_PLIST}" 1167 | else 1168 | echo "" > "/Library/LaunchDaemons/${APP_PLIST}" 1169 | echo "> "/Library/LaunchDaemons/${APP_PLIST}" 1170 | echo "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" >> "/Library/LaunchDaemons/${APP_PLIST}" 1171 | echo "" >> "/Library/LaunchDaemons/${APP_PLIST}" 1172 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1173 | echo " Label" >> "/Library/LaunchDaemons/${APP_PLIST}" 1174 | echo " ${APP_PLIST_BASE}" >> "/Library/LaunchDaemons/${APP_PLIST}" 1175 | echo " ProgramArguments" >> "/Library/LaunchDaemons/${APP_PLIST}" 1176 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1177 | echo " ${REALDIR}/${APP_NAME}" >> "/Library/LaunchDaemons/${APP_PLIST}" 1178 | echo " launchdinternal" >> "/Library/LaunchDaemons/${APP_PLIST}" 1179 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1180 | echo " OnDemand" >> "/Library/LaunchDaemons/${APP_PLIST}" 1181 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1182 | echo " RunAtLoad" >> "/Library/LaunchDaemons/${APP_PLIST}" 1183 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1184 | if [ "X$RUN_AS_USER" != "X" ] ; then 1185 | echo " UserName" >> "/Library/LaunchDaemons/${APP_PLIST}" 1186 | echo " ${RUN_AS_USER}" >> "/Library/LaunchDaemons/${APP_PLIST}" 1187 | fi 1188 | echo " " >> "/Library/LaunchDaemons/${APP_PLIST}" 1189 | echo "" >> "/Library/LaunchDaemons/${APP_PLIST}" 1190 | fi 1191 | chmod 555 "/Library/LaunchDaemons/${APP_PLIST}" 1192 | fi 1193 | elif [ "$DIST_OS" = "zos" ] ; then 1194 | eval echo `gettext 'Detected z/OS:'` 1195 | if [ -f /etc/rc.bak ] ; then 1196 | eval echo `gettext ' The $APP_LONG_NAME daemon is already installed.'` 1197 | exit 1 1198 | else 1199 | eval echo `gettext ' Installing the $APP_LONG_NAME daemon..'` 1200 | cp /etc/rc /etc/rc.bak 1201 | sed "s:echo /etc/rc script executed, \`date\`::g" /etc/rc.bak > /etc/rc 1202 | echo "_BPX_JOBNAME='${APP_NAME}' \"${REALDIR}/${APP_NAME}\" start" >>/etc/rc 1203 | echo '/etc/rc script executed, `date`' >>/etc/rc 1204 | fi 1205 | else 1206 | eval echo `gettext 'Install not currently supported for $DIST_OS'` 1207 | exit 1 1208 | fi 1209 | fi 1210 | } 1211 | 1212 | removedaemon() { 1213 | if [ `id | sed 's/^uid=//;s/(.*$//'` != "0" ] ; then 1214 | eval echo `gettext 'Must be root to perform this action.'` 1215 | exit 1 1216 | else 1217 | stopit "0" 1218 | APP_NAME_LOWER=`echo "$APP_NAME" | tr "[A-Z]" "[a-z]"` 1219 | if [ "$DIST_OS" = "solaris" ] ; then 1220 | eval echo `gettext 'Detected Solaris:'` 1221 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1222 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1223 | for i in "/etc/rc3.d/S20$APP_NAME_LOWER" "/etc/rc3.d/K20$APP_NAME_LOWER" "/etc/init.d/$APP_NAME" 1224 | do 1225 | rm -f $i 1226 | done 1227 | else 1228 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1229 | exit 1 1230 | fi 1231 | elif [ "$DIST_OS" = "linux" ] ; then 1232 | if [ -f /etc/redhat-release -o -f /etc/redhat_version -o -f /etc/fedora-release ] ; then 1233 | eval echo `gettext 'Detected RHEL or Fedora:'` 1234 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1235 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1236 | /sbin/chkconfig "$APP_NAME" off 1237 | /sbin/chkconfig --del "$APP_NAME" 1238 | rm -f "/etc/init.d/$APP_NAME" 1239 | elif [ -f "/etc/init/${APP_NAME}.conf" ] ; then 1240 | eval echo `gettext ' Removing $APP_LONG_NAME daemon from upstart...'` 1241 | rm "/etc/init/${APP_NAME}.conf" 1242 | else 1243 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1244 | exit 1 1245 | fi 1246 | elif [ -f /etc/SuSE-release ] ; then 1247 | eval echo `gettext 'Detected SuSE or SLES:'` 1248 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1249 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1250 | insserv -r "/etc/init.d/$APP_NAME" 1251 | rm -f "/etc/init.d/$APP_NAME" 1252 | else 1253 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1254 | exit 1 1255 | fi 1256 | elif [ -f /etc/lsb-release ] ; then 1257 | eval echo `gettext 'Detected Ubuntu:'` 1258 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1259 | eval echo `gettext ' Removing $APP_LONG_NAME daemon from init.d...'` 1260 | update-rc.d -f "$APP_NAME" remove 1261 | rm -f "/etc/init.d/$APP_NAME" 1262 | elif [ -f "/etc/init/${APP_NAME}.conf" ] ; then 1263 | eval echo `gettext ' Removing $APP_LONG_NAME daemon from upstart...'` 1264 | rm "/etc/init/${APP_NAME}.conf" 1265 | else 1266 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1267 | exit 1 1268 | fi 1269 | else 1270 | eval echo `gettext 'Detected Linux:'` 1271 | if [ -f "/etc/init.d/$APP_NAME" ] ; then 1272 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1273 | for i in "/etc/rc3.d/K20$APP_NAME_LOWER" "/etc/rc5.d/K20$APP_NAME_LOWER" "/etc/rc3.d/S20$APP_NAME_LOWER" "/etc/init.d/$APP_NAME" "/etc/rc5.d/S20$APP_NAME_LOWER" 1274 | do 1275 | rm -f $i 1276 | done 1277 | else 1278 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1279 | exit 1 1280 | fi 1281 | fi 1282 | elif [ "$DIST_OS" = "hpux" ] ; then 1283 | eval echo `gettext 'Detected HP-UX:'` 1284 | if [ -f "/sbin/init.d/$APP_NAME" ] ; then 1285 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1286 | for i in "/sbin/rc3.d/K20$APP_NAME_LOWER" "/sbin/rc3.d/S20$APP_NAME_LOWER" "/sbin/init.d/$APP_NAME" 1287 | do 1288 | rm -f $i 1289 | done 1290 | else 1291 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1292 | exit 1 1293 | fi 1294 | elif [ "$DIST_OS" = "aix" ] ; then 1295 | eval echo `gettext 'Detected AIX:'` 1296 | if [ -f "/etc/rc.d/init.d/$APP_NAME" -o -n "`/usr/sbin/lsitab $APP_NAME`" -o -n "`/usr/bin/lssrc -S -s $APP_NAME`" ] ; then 1297 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1298 | if [ -f "/etc/rc.d/init.d/$APP_NAME" ] ; then 1299 | for i in "/etc/rc.d/rc2.d/S20$APP_NAME_LOWER" "/etc/rc.d/rc2.d/K20$APP_NAME_LOWER" "/etc/rc.d/init.d/$APP_NAME" 1300 | do 1301 | rm -f $i 1302 | done 1303 | fi 1304 | if [ -n "`/usr/sbin/lsitab $APP_NAME`" -o -n "`/usr/bin/lssrc -S -s $APP_NAME`" ] ; then 1305 | /usr/sbin/rmitab $APP_NAME 1306 | /usr/bin/rmssys -s $APP_NAME 1307 | fi 1308 | else 1309 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1310 | exit 1 1311 | fi 1312 | elif [ "$DIST_OS" = "freebsd" ] ; then 1313 | eval echo `gettext 'Detected FreeBSD:'` 1314 | if [ -f "/etc/rc.d/$APP_NAME" ] ; then 1315 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1316 | for i in "/etc/rc.d/$APP_NAME" 1317 | do 1318 | rm -f $i 1319 | done 1320 | sed -i .bak "/${APP_NAME}_enable=\"YES\"/d" /etc/rc.conf 1321 | else 1322 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1323 | exit 1 1324 | fi 1325 | elif [ "$DIST_OS" = "macosx" ] ; then 1326 | eval echo `gettext 'Detected Mac OSX:'` 1327 | if [ -f "/Library/LaunchDaemons/${APP_PLIST}" ] ; then 1328 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1329 | # Make sure the plist is installed 1330 | LOADED_PLIST=`launchctl list | grep ${APP_PLIST_BASE}` 1331 | if [ "X${LOADED_PLIST}" != "X" ] ; then 1332 | launchctl unload "/Library/LaunchDaemons/${APP_PLIST}" 1333 | fi 1334 | rm -f "/Library/LaunchDaemons/${APP_PLIST}" 1335 | else 1336 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1337 | exit 1 1338 | fi 1339 | elif [ "$DIST_OS" = "zos" ] ; then 1340 | eval echo `gettext 'Detected z/OS:'` 1341 | if [ -f /etc/rc.bak ] ; then 1342 | eval echo `gettext ' Removing $APP_LONG_NAME daemon...'` 1343 | cp /etc/rc /etc/rc.bak 1344 | sed "s/_BPX_JOBNAME=\'APP_NAME\'.*//g" /etc/rc.bak > /etc/rc 1345 | rm /etc/rc.bak 1346 | else 1347 | eval echo `gettext ' The $APP_LONG_NAME daemon is not currently installed.'` 1348 | exit 1 1349 | fi 1350 | else 1351 | eval echo `gettext 'Remove not currently supported for $DIST_OS'` 1352 | exit 1 1353 | fi 1354 | fi 1355 | } 1356 | 1357 | dump() { 1358 | eval echo `gettext 'Dumping $APP_LONG_NAME...'` 1359 | getpid 1360 | if [ "X$pid" = "X" ] 1361 | then 1362 | eval echo `gettext '$APP_LONG_NAME was not running.'` 1363 | else 1364 | kill -3 $pid 1365 | 1366 | if [ $? -ne 0 ] 1367 | then 1368 | eval echo `gettext 'Failed to dump $APP_LONG_NAME.'` 1369 | exit 1 1370 | else 1371 | eval echo `gettext 'Dumped $APP_LONG_NAME.'` 1372 | fi 1373 | fi 1374 | } 1375 | 1376 | # Used by HP-UX init scripts. 1377 | startmsg() { 1378 | getpid 1379 | if [ "X$pid" = "X" ] 1380 | then 1381 | eval echo `gettext 'Starting $APP_LONG_NAME... Wrapper:Stopped'` 1382 | else 1383 | if [ "X$DETAIL_STATUS" = "X" ] 1384 | then 1385 | eval echo `gettext 'Starting $APP_LONG_NAME... Wrapper:Running'` 1386 | else 1387 | getstatus 1388 | eval echo `gettext 'Starting $APP_LONG_NAME... Wrapper:$STATUS, Java:$JAVASTATUS'` 1389 | fi 1390 | fi 1391 | } 1392 | 1393 | # Used by HP-UX init scripts. 1394 | stopmsg() { 1395 | getpid 1396 | if [ "X$pid" = "X" ] 1397 | then 1398 | eval echo `gettext 'Stopping $APP_LONG_NAME... Wrapper:Stopped'` 1399 | else 1400 | if [ "X$DETAIL_STATUS" = "X" ] 1401 | then 1402 | eval echo `gettext 'Stopping $APP_LONG_NAME... Wrapper:Running'` 1403 | else 1404 | getstatus 1405 | eval echo `gettext 'Stopping $APP_LONG_NAME... Wrapper:$STATUS, Java:$JAVASTATUS'` 1406 | fi 1407 | fi 1408 | } 1409 | 1410 | showUsage() { 1411 | # $1 bad command 1412 | 1413 | if [ -n "$1" ] 1414 | then 1415 | eval echo `gettext 'Unexpected command: $1'` 1416 | echo ""; 1417 | fi 1418 | 1419 | eval MSG=`gettext 'Usage: '` 1420 | if [ -n "$FIXED_COMMAND" ] ; then 1421 | if [ -n "$PASS_THROUGH" ] ; then 1422 | echo "${MSG} $0 {JavaAppArgs}" 1423 | else 1424 | echo "${MSG} $0" 1425 | fi 1426 | else 1427 | if [ -n "$PAUSABLE" ] ; then 1428 | if [ -n "$PASS_THROUGH" ] ; then 1429 | echo "${MSG} $0 [ console {JavaAppArgs} | start {JavaAppArgs} | stop | restart {JavaAppArgs} | condrestart {JavaAppArgs} | pause | resume | status | install | remove | dump ]" 1430 | else 1431 | echo "${MSG} $0 [ console | start | stop | restart | condrestart | pause | resume | status | install | remove | dump ]" 1432 | fi 1433 | else 1434 | if [ -n "$PASS_THROUGH" ] ; then 1435 | echo "${MSG} $0 [ console {JavaAppArgs} | start {JavaAppArgs} | stop | restart {JavaAppArgs} | condrestart {JavaAppArgs} | status | install | remove | dump ]" 1436 | else 1437 | echo "${MSG} $0 [ console | start | stop | restart | condrestart | status | install | remove | dump ]" 1438 | fi 1439 | fi 1440 | fi 1441 | 1442 | if [ ! -n "$BRIEF_USAGE" ] 1443 | then 1444 | echo ""; 1445 | if [ ! -n "$FIXED_COMMAND" ] ; then 1446 | echo "`gettext 'Commands:'`" 1447 | echo "`gettext ' console Launch in the current console.'`" 1448 | echo "`gettext ' start Start in the background as a daemon process.'`" 1449 | echo "`gettext ' stop Stop if running as a daemon or in another console.'`" 1450 | echo "`gettext ' restart Stop if running and then start.'`" 1451 | echo "`gettext ' condrestart Restart only if already running.'`" 1452 | if [ -n "$PAUSABLE" ] ; then 1453 | echo "`gettext ' pause Pause if running.'`" 1454 | echo "`gettext ' resume Resume if paused.'`" 1455 | fi 1456 | echo "`gettext ' status Query the current status.'`" 1457 | echo "`gettext ' install Install to start automatically when system boots.'`" 1458 | echo "`gettext ' remove Uninstall.'`" 1459 | echo "`gettext ' dump Request a Java thread dump if running.'`" 1460 | echo ""; 1461 | fi 1462 | if [ -n "$PASS_THROUGH" ] ; then 1463 | echo "`gettext 'JavaAppArgs: Zero or more arguments which will be passed to the Java application.'`" 1464 | echo ""; 1465 | fi 1466 | fi 1467 | 1468 | exit 1 1469 | } 1470 | 1471 | docommand() { 1472 | case "$COMMAND" in 1473 | 'console') 1474 | checkUser touchlock "$@" 1475 | if [ ! -n "$FIXED_COMMAND" ] ; then 1476 | shift 1477 | fi 1478 | console "$@" 1479 | ;; 1480 | 1481 | 'start') 1482 | if [ "$DIST_OS" = "macosx" -a -f "/Library/LaunchDaemons/${APP_PLIST}" ] ; then 1483 | macosxstart 1484 | elif [ "$DIST_OS" = "linux" -a -f "/etc/init/${APP_NAME}.conf" ] ; then 1485 | checkUser touchlock "$@" 1486 | upstartstart 1487 | else 1488 | checkUser touchlock "$@" 1489 | if [ ! -n "$FIXED_COMMAND" ] ; then 1490 | shift 1491 | fi 1492 | start "$@" 1493 | fi 1494 | ;; 1495 | 1496 | 'stop') 1497 | checkUser "" "$COMMAND" 1498 | stopit "0" 1499 | ;; 1500 | 1501 | 'restart') 1502 | checkUser touchlock "$COMMAND" 1503 | if [ ! -n "$FIXED_COMMAND" ] ; then 1504 | shift 1505 | fi 1506 | stopit "0" 1507 | start "$@" 1508 | ;; 1509 | 1510 | 'condrestart') 1511 | checkUser touchlock "$COMMAND" 1512 | if [ ! -n "$FIXED_COMMAND" ] ; then 1513 | shift 1514 | fi 1515 | stopit "1" 1516 | start "$@" 1517 | ;; 1518 | 1519 | 'pause') 1520 | if [ -n "$PAUSABLE" ] 1521 | then 1522 | pause 1523 | else 1524 | showUsage "$COMMAND" 1525 | fi 1526 | ;; 1527 | 1528 | 'resume') 1529 | if [ -n "$PAUSABLE" ] 1530 | then 1531 | resume 1532 | else 1533 | showUsage "$COMMAND" 1534 | fi 1535 | ;; 1536 | 1537 | 'status') 1538 | checkUser "" "$COMMAND" 1539 | status 1540 | ;; 1541 | 1542 | 'install') 1543 | installdaemon 1544 | ;; 1545 | 1546 | 'remove') 1547 | removedaemon 1548 | ;; 1549 | 1550 | 'dump') 1551 | checkUser "" "$COMMAND" 1552 | dump 1553 | ;; 1554 | 1555 | 'start_msg') 1556 | # Internal command called by launchd on HP-UX. 1557 | checkUser "" "$COMMAND" 1558 | startmsg 1559 | ;; 1560 | 1561 | 'stop_msg') 1562 | # Internal command called by launchd on HP-UX. 1563 | checkUser "" "$COMMAND" 1564 | stopmsg 1565 | ;; 1566 | 1567 | 'launchdinternal') 1568 | # Internal command called by launchd on Max OSX. 1569 | # We do not want to call checkUser here as it is handled in the launchd plist file. Doing it here would confuse launchd. 1570 | if [ ! -n "$FIXED_COMMAND" ] ; then 1571 | shift 1572 | fi 1573 | launchdinternal "$@" 1574 | ;; 1575 | 1576 | *) 1577 | showUsage "$COMMAND" 1578 | ;; 1579 | esac 1580 | } 1581 | 1582 | docommand "$@" 1583 | 1584 | exit 0 1585 | -------------------------------------------------------------------------------- /bin/wrapper-linux-x86-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-linux-x86-32 -------------------------------------------------------------------------------- /bin/wrapper-linux-x86-64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-linux-x86-64 -------------------------------------------------------------------------------- /bin/wrapper-macosx-arm-64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-macosx-arm-64 -------------------------------------------------------------------------------- /bin/wrapper-macosx-universal-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-macosx-universal-32 -------------------------------------------------------------------------------- /bin/wrapper-macosx-universal-64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-macosx-universal-64 -------------------------------------------------------------------------------- /bin/wrapper-windows-x86-32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-windows-x86-32.dll -------------------------------------------------------------------------------- /bin/wrapper-windows-x86-32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-windows-x86-32.exe -------------------------------------------------------------------------------- /bin/wrapper-windows-x86-64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-windows-x86-64.dll -------------------------------------------------------------------------------- /bin/wrapper-windows-x86-64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper-windows-x86-64.exe -------------------------------------------------------------------------------- /bin/wrapper.conf: -------------------------------------------------------------------------------- 1 | #******************************************************************** 2 | # Wrapper Properties 3 | # 4 | # NOTE - Please use src/conf/wrapper.conf.in as a template for your 5 | # own application rather than the values used for the 6 | # TestWrapper sample. 7 | #******************************************************************** 8 | 9 | #environment variable 10 | set.psqueue.home=. 11 | 12 | #Advanced Configuration Properties 13 | wrapper.ignore_sequence_gaps=TRUE 14 | wrapper.shutdown.timeout=60 15 | wrapper.jvm_exit.timeout=30 16 | wrapper.ping.timeout=300 17 | 18 | # Java Application 19 | wrapper.java.command=%JAVA_HOME%/bin/java 20 | wrapper.working.dir=../ 21 | 22 | # Java Main class. This class must implement the WrapperListener interface 23 | # or guarantee that the WrapperManager class is initialized. Helper 24 | # classes are provided to do this for you. See the Integration section 25 | # of the documentation for details. 26 | wrapper.java.mainclass=wjw.psqueue.server.Wrapper 27 | 28 | # Java Classpath (include wrapper.jar) Add class path elements as 29 | # needed starting from 1 30 | wrapper.java.classpath.1=bin/wrapper.jar 31 | wrapper.java.classpath.2=./lib/*.jar 32 | wrapper.java.classpath.3=./classes 33 | 34 | # Java Library Path (location of Wrapper.DLL or libwrapper.so) 35 | wrapper.java.library.path.1=bin 36 | wrapper.java.library.path.2=. 37 | 38 | # Java Additional Parameters 39 | wrapper.java.additional.1=-Dfile.encoding=UTF-8 40 | wrapper.java.additional.2=-Duser.timezone=GMT+8 41 | wrapper.java.additional.3=-Duser.language=zh 42 | wrapper.java.additional.4=-Duser.country=CN 43 | wrapper.java.additional.5=-Djava.net.preferIPv4Stack=true 44 | 45 | wrapper.java.additional.6=-Dorg.tanukisoftware.wrapper.WrapperManager.mbean=true 46 | wrapper.java.additional.7=-Dorg.tanukisoftware.wrapper.WrapperManager.mbean.testing=false 47 | wrapper.java.additional.8=-DJEMonitor=true 48 | 49 | wrapper.java.additional.9=-server 50 | wrapper.java.additional.10=-Xms1g 51 | wrapper.java.additional.11=-Xmx1g 52 | wrapper.java.additional.12=-XX:+UseG1GC 53 | wrapper.java.additional.13=-XX:MaxGCPauseMillis=10 54 | wrapper.java.additional.14=-XX:GCPauseIntervalMillis=200 55 | 56 | # Initial Java Heap Size (in MB) 57 | #wrapper.java.initmemory=3 58 | 59 | # Maximum Java Heap Size (in MB) 60 | #wrapper.java.maxmemory=128 61 | 62 | # Application parameters. Add parameters as needed starting from 1 63 | wrapper.app.parameter.1=wjw.psqueue.server.App 64 | 65 | #******************************************************************** 66 | # Wrapper Logging Properties 67 | #******************************************************************** 68 | # Format of output for the console. (See docs for formats) 69 | wrapper.console.format=PM 70 | 71 | # Log Level for console output. (See docs for log levels) 72 | wrapper.console.loglevel=INFO 73 | 74 | # Log file to use for wrapper output logging. 75 | wrapper.logfile=%psqueue.home%/log/wrapper-psqueue.log 76 | 77 | # Format of output for the log file. (See docs for formats) 78 | wrapper.logfile.format=LPTM 79 | 80 | # Log Level for log file output. (See docs for log levels) 81 | wrapper.logfile.loglevel=INFO 82 | 83 | # Maximum size that the log file will be allowed to grow to before 84 | # the log is rolled. Size is specified in bytes. The default value 85 | # of 0, disables log rolling. May abbreviate with the 'k' (kb) or 86 | # 'm' (mb) suffix. For example: 10m = 10 megabytes. 87 | #This property is ignored unless the wrapper.logfile.rollmode property has a value of SIZE, SIZE_OR_WRAPPER, or SIZE_OR_JVM. 88 | wrapper.logfile.maxsize=100m 89 | 90 | # Controls the roll mode of the log file. (See docs for formats) 91 | wrapper.logfile.rollmode=SIZE 92 | 93 | # Maximum number of rolled log files which will be allowed before old 94 | # files are deleted. The default value of 0 implies no limit. 95 | wrapper.logfile.maxfiles=10 96 | 97 | # Log Level for sys/event log output. (See docs for log levels) 98 | wrapper.syslog.loglevel=NONE 99 | 100 | #******************************************************************** 101 | # Wrapper Windows Properties 102 | #******************************************************************** 103 | # Title to use when running as a console 104 | wrapper.console.title=psqueue server 105 | 106 | #******************************************************************** 107 | # Wrapper Windows NT/2000/XP Service Properties 108 | #******************************************************************** 109 | # WARNING - Do not modify any of these properties when an application 110 | # using this configuration file has been installed as a service. 111 | # Please uninstall the service before modifying this section. The 112 | # service can then be reinstalled. 113 | 114 | # Name of the service 115 | wrapper.ntservice.name=psqueue 116 | 117 | # Display name of the service 118 | wrapper.ntservice.displayname=psqueue 119 | 120 | # Description of the service 121 | wrapper.ntservice.description=psqueue 122 | 123 | # Service dependencies. Add dependencies as needed starting from 1 124 | wrapper.ntservice.dependency.1= 125 | 126 | # Mode in which the service is installed. AUTO_START or DEMAND_START 127 | wrapper.ntservice.starttype=AUTO_START 128 | 129 | # Allow the service to interact with the desktop. 130 | wrapper.ntservice.interactive=false 131 | -------------------------------------------------------------------------------- /bin/wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/bin/wrapper.jar -------------------------------------------------------------------------------- /conf/conf.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | * 4 | 1818 5 | 200 6 | 60 7 | UTF-8 8 | 30 9 | admin 10 | 123456 11 | 1819 12 | 13 | -------------------------------------------------------------------------------- /conf/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /help-doc/Test-URL.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/help-doc/Test-URL.txt -------------------------------------------------------------------------------- /lib/EasyFastJson-2.7.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/EasyFastJson-2.7.2.jar -------------------------------------------------------------------------------- /lib/istack-commons-runtime.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/istack-commons-runtime.jar -------------------------------------------------------------------------------- /lib/javax.activation-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/javax.activation-api.jar -------------------------------------------------------------------------------- /lib/jaxb-api-2.3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/jaxb-api-2.3.1.jar -------------------------------------------------------------------------------- /lib/jaxb-runtime.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/jaxb-runtime.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/log4j-1.2.17.jar -------------------------------------------------------------------------------- /lib/netty-all-4.1.7.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/netty-all-4.1.7.Final.jar -------------------------------------------------------------------------------- /lib/slf4j-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/slf4j-api.jar -------------------------------------------------------------------------------- /lib/slf4j-log4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/lib/slf4j-log4j.jar -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/BigArrayImpl.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.math.BigInteger; 6 | import java.nio.ByteBuffer; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | import java.util.concurrent.locks.Lock; 9 | import java.util.concurrent.locks.ReadWriteLock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | import java.util.concurrent.locks.ReentrantReadWriteLock; 12 | 13 | import com.leansoft.bigqueue.page.IMappedPage; 14 | import com.leansoft.bigqueue.page.IMappedPageFactory; 15 | import com.leansoft.bigqueue.page.MappedPageFactoryImpl; 16 | import com.leansoft.bigqueue.utils.Calculator; 17 | import com.leansoft.bigqueue.utils.FileUtil; 18 | 19 | /** 20 | * A big array implementation supporting sequential append and random read. 21 | * 22 | * Main features: 23 | * 1. FAST : close to the speed of direct memory access, extremely fast in append only and sequential read modes, 24 | * sequential append and read are close to O(1) memory access, random read is close to O(1) memory access if 25 | * data is in cache and is close to O(1) disk access if data is not in cache. 26 | * 2. MEMORY-EFFICIENT : automatic paging & swapping algorithm, only most-recently accessed data is kept in memory. 27 | * 3. THREAD-SAFE : multiple threads can concurrently read/append the array without data corruption. 28 | * 4. PERSISTENT - all array data is persisted on disk, and is crash resistant. 29 | * 5. BIG(HUGE) - the total size of the array data is only limited by the available disk space. 30 | * 31 | * 32 | * @author bulldog 33 | * 34 | */ 35 | public class BigArrayImpl implements IBigArray { 36 | 37 | // folder name for index page 38 | final static String INDEX_PAGE_FOLDER = "index"; 39 | // folder name for data page 40 | final static String DATA_PAGE_FOLDER = "data"; 41 | // folder name for meta data page 42 | final static String META_DATA_PAGE_FOLDER = "meta_data"; 43 | 44 | // 2 ^ 20 = 1024 * 1024 45 | final static int INDEX_ITEMS_PER_PAGE_BITS = 20; // 1024 * 1024 46 | // number of items per page 47 | final static int INDEX_ITEMS_PER_PAGE = 1 << INDEX_ITEMS_PER_PAGE_BITS; 48 | // 2 ^ 5 = 32 49 | final static int INDEX_ITEM_LENGTH_BITS = 5; 50 | // length in bytes of an index item 51 | final static int INDEX_ITEM_LENGTH = 1 << INDEX_ITEM_LENGTH_BITS; 52 | // size in bytes of an index page 53 | final static int INDEX_PAGE_SIZE = INDEX_ITEM_LENGTH * INDEX_ITEMS_PER_PAGE; 54 | 55 | // size in bytes of a data page 56 | final int DATA_PAGE_SIZE; 57 | 58 | // default size in bytes of a data page 59 | public final static int DEFAULT_DATA_PAGE_SIZE = 128 * 1024 * 1024; 60 | // minimum size in bytes of a data page 61 | public final static int MINIMUM_DATA_PAGE_SIZE = 32 * 1024 * 1024; 62 | // seconds, time to live for index page cached in memory 63 | final static int INDEX_PAGE_CACHE_TTL = 1000; 64 | // seconds, time to live for data page cached in memory 65 | final static int DATA_PAGE_CACHE_TTL = 1000; 66 | // 2 ^ 4 = 16 67 | final static int META_DATA_ITEM_LENGTH_BITS = 4; 68 | // size in bytes of a meta data page 69 | final static int META_DATA_PAGE_SIZE = 1 << META_DATA_ITEM_LENGTH_BITS; 70 | 71 | // private final static int INDEX_ITEM_DATA_PAGE_INDEX_OFFSET = 0; 72 | // private final static int INDEX_ITEM_DATA_ITEM_OFFSET_OFFSET = 8; 73 | private final static int INDEX_ITEM_DATA_ITEM_LENGTH_OFFSET = 12; 74 | // timestamp offset of an data item within an index item 75 | final static int INDEX_ITEM_DATA_ITEM_TIMESTAMP_OFFSET = 16; 76 | 77 | // directory to persist array data 78 | String arrayDirectory; 79 | 80 | // factory for index page management(acquire, release, cache) 81 | IMappedPageFactory indexPageFactory; 82 | // factory for data page management(acquire, release, cache) 83 | IMappedPageFactory dataPageFactory; 84 | // factory for meta data page management(acquire, release, cache) 85 | IMappedPageFactory metaPageFactory; 86 | 87 | // only use the first page 88 | static final long META_DATA_PAGE_INDEX = 0; 89 | 90 | // head index of the big array, this is the read write barrier. 91 | // readers can only read items before this index, and writes can write this index or after 92 | final AtomicLong arrayHeadIndex = new AtomicLong(); 93 | // tail index of the big array, 94 | // readers can't read items before this tail 95 | final AtomicLong arrayTailIndex = new AtomicLong(); 96 | 97 | // head index of the data page, this is the to be appended data page index 98 | long headDataPageIndex; 99 | // head offset of the data page, this is the to be appended data offset 100 | int headDataItemOffset; 101 | 102 | // lock for appending state management 103 | final Lock appendLock = new ReentrantLock(); 104 | 105 | // global lock for array read and write management 106 | final ReadWriteLock arrayReadWritelock = new ReentrantReadWriteLock(); 107 | final Lock arrayReadLock = arrayReadWritelock.readLock(); 108 | final Lock arrayWriteLock = arrayReadWritelock.writeLock(); 109 | 110 | /** 111 | * 112 | * A big array implementation supporting sequential write and random read, 113 | * use default back data file size per page, see {@link #DEFAULT_DATA_PAGE_SIZE}. 114 | * 115 | * @param arrayDir directory for array data store 116 | * @param arrayName the name of the array, will be appended as last part of the array directory 117 | * @throws IOException exception throws during array initialization 118 | */ 119 | public BigArrayImpl(String arrayDir, String arrayName) throws IOException { 120 | this(arrayDir, arrayName, DEFAULT_DATA_PAGE_SIZE); 121 | } 122 | 123 | /** 124 | * A big array implementation supporting sequential write and random read. 125 | * 126 | * @param arrayDir directory for array data store 127 | * @param arrayName the name of the array, will be appended as last part of the array directory 128 | * @param pageSize the back data file size per page in bytes, see minimum allowed {@link #MINIMUM_DATA_PAGE_SIZE}. 129 | * @throws IOException exception throws during array initialization 130 | */ 131 | public BigArrayImpl(String arrayDir, String arrayName, int pageSize) throws IOException { 132 | arrayDirectory = arrayDir; 133 | if (!arrayDirectory.endsWith(File.separator)) { 134 | arrayDirectory += File.separator; 135 | } 136 | // append array name as part of the directory 137 | arrayDirectory = arrayDirectory + arrayName + File.separator; 138 | 139 | // validate directory 140 | if (!FileUtil.isFilenameValid(arrayDirectory)) { 141 | throw new IllegalArgumentException("invalid array directory : " + arrayDirectory); 142 | } 143 | 144 | if (pageSize < MINIMUM_DATA_PAGE_SIZE) { 145 | throw new IllegalArgumentException("invalid page size, allowed minimum is : " + MINIMUM_DATA_PAGE_SIZE + " bytes."); 146 | } 147 | 148 | DATA_PAGE_SIZE = pageSize; 149 | 150 | this.commonInit(); 151 | } 152 | 153 | public String getArrayDirectory() { 154 | return this.arrayDirectory; 155 | } 156 | 157 | 158 | void commonInit() throws IOException { 159 | // initialize page factories 160 | this.indexPageFactory = new MappedPageFactoryImpl(INDEX_PAGE_SIZE, 161 | this.arrayDirectory + INDEX_PAGE_FOLDER, 162 | INDEX_PAGE_CACHE_TTL); 163 | this.dataPageFactory = new MappedPageFactoryImpl(DATA_PAGE_SIZE, 164 | this.arrayDirectory + DATA_PAGE_FOLDER, 165 | DATA_PAGE_CACHE_TTL); 166 | // the ttl does not matter here since meta data page is always cached 167 | this.metaPageFactory = new MappedPageFactoryImpl(META_DATA_PAGE_SIZE, 168 | this.arrayDirectory + META_DATA_PAGE_FOLDER, 169 | 10 * 1000/*does not matter*/); 170 | 171 | // initialize array indexes 172 | initArrayIndex(); 173 | 174 | // initialize data page indexes 175 | initDataPageIndex(); 176 | } 177 | 178 | @Override 179 | public void removeAll() throws IOException { 180 | try { 181 | arrayWriteLock.lock(); 182 | this.indexPageFactory.deleteAllPages(); 183 | this.dataPageFactory.deleteAllPages(); 184 | this.metaPageFactory.deleteAllPages(); 185 | //FileUtil.deleteDirectory(new File(this.arrayDirectory)); 186 | 187 | this.commonInit(); 188 | } finally { 189 | arrayWriteLock.unlock(); 190 | } 191 | } 192 | 193 | @Override 194 | public void removeBeforeIndex(long index) throws IOException { 195 | try { 196 | arrayWriteLock.lock(); 197 | 198 | validateIndex(index); 199 | 200 | long indexPageIndex = Calculator.div(index, INDEX_ITEMS_PER_PAGE_BITS); 201 | 202 | ByteBuffer indexItemBuffer = this.getIndexItemBuffer(index); 203 | long dataPageIndex = indexItemBuffer.getLong(); 204 | 205 | if (indexPageIndex > 0L) { 206 | this.indexPageFactory.deletePagesBeforePageIndex(indexPageIndex); 207 | } 208 | if (dataPageIndex > 0L) { 209 | this.dataPageFactory.deletePagesBeforePageIndex(dataPageIndex); 210 | } 211 | 212 | // advance the tail to index 213 | this.arrayTailIndex.set(index); 214 | } finally { 215 | arrayWriteLock.unlock(); 216 | } 217 | } 218 | 219 | 220 | 221 | @Override 222 | public void removeBefore(long timestamp) throws IOException { 223 | try { 224 | arrayWriteLock.lock(); 225 | long firstIndexPageIndex = this.indexPageFactory.getFirstPageIndexBefore(timestamp); 226 | if (firstIndexPageIndex >= 0) { 227 | // long nextIndexPageIndex = firstIndexPageIndex; 228 | // if (nextIndexPageIndex == Long.MAX_VALUE) { //wrap 229 | // nextIndexPageIndex = 0L; 230 | // } else { 231 | // nextIndexPageIndex++; 232 | // } 233 | long toRemoveBeforeIndex = Calculator.mul(firstIndexPageIndex, INDEX_ITEMS_PER_PAGE_BITS); 234 | removeBeforeIndex(toRemoveBeforeIndex); 235 | } 236 | } catch (IndexOutOfBoundsException ex) { 237 | // ignore 238 | } finally { 239 | arrayWriteLock.unlock(); 240 | } 241 | } 242 | 243 | // find out array head/tail from the meta data 244 | void initArrayIndex() throws IOException { 245 | IMappedPage metaDataPage = this.metaPageFactory.acquirePage(META_DATA_PAGE_INDEX); 246 | ByteBuffer metaBuf = metaDataPage.getLocal(0); 247 | long head = metaBuf.getLong(); 248 | long tail = metaBuf.getLong(); 249 | 250 | arrayHeadIndex.set(head); 251 | arrayTailIndex.set(tail); 252 | } 253 | 254 | // find out data page head index and offset 255 | void initDataPageIndex() throws IOException { 256 | 257 | if (this.isEmpty()) { 258 | headDataPageIndex = 0L; 259 | headDataItemOffset = 0; 260 | } else { 261 | IMappedPage previousIndexPage = null; 262 | long previousIndexPageIndex = -1; 263 | try { 264 | long previousIndex = this.arrayHeadIndex.get() - 1; 265 | if (previousIndex < 0) { 266 | previousIndex = Long.MAX_VALUE; // wrap 267 | } 268 | previousIndexPageIndex = Calculator.div(previousIndex, INDEX_ITEMS_PER_PAGE_BITS); // shift optimization 269 | previousIndexPage = this.indexPageFactory.acquirePage(previousIndexPageIndex); 270 | int previousIndexPageOffset = (int) (Calculator.mul(Calculator.mod(previousIndex, INDEX_ITEMS_PER_PAGE_BITS), INDEX_ITEM_LENGTH_BITS)); 271 | ByteBuffer previousIndexItemBuffer = previousIndexPage.getLocal(previousIndexPageOffset); 272 | long previousDataPageIndex = previousIndexItemBuffer.getLong(); 273 | int previousDataItemOffset = previousIndexItemBuffer.getInt(); 274 | int perviousDataItemLength = previousIndexItemBuffer.getInt(); 275 | 276 | headDataPageIndex = previousDataPageIndex; 277 | headDataItemOffset = previousDataItemOffset + perviousDataItemLength; 278 | } finally { 279 | if (previousIndexPage != null) { 280 | this.indexPageFactory.releasePage(previousIndexPageIndex); 281 | } 282 | } 283 | } 284 | } 285 | 286 | /** 287 | * Append the data into the head of the array 288 | */ 289 | public long append(byte[] data) throws IOException { 290 | try { 291 | arrayReadLock.lock(); 292 | IMappedPage toAppendDataPage = null; 293 | IMappedPage toAppendIndexPage = null; 294 | long toAppendIndexPageIndex = -1L; 295 | long toAppendDataPageIndex = -1L; 296 | 297 | long toAppendArrayIndex = -1L; 298 | 299 | try { 300 | appendLock.lock(); // only one thread can append 301 | 302 | if (this.isFull()) { // end of the world check:) 303 | throw new IOException("ring space of java long type used up, the end of the world!!!"); 304 | } 305 | 306 | // prepare the data pointer 307 | if (this.headDataItemOffset + data.length > DATA_PAGE_SIZE) { // not enough space 308 | if (this.headDataPageIndex == Long.MAX_VALUE) { 309 | this.headDataPageIndex = 0L; // wrap 310 | } else { 311 | this.headDataPageIndex++; 312 | } 313 | this.headDataItemOffset = 0; 314 | } 315 | 316 | toAppendDataPageIndex = this.headDataPageIndex; 317 | int toAppendDataItemOffset = this.headDataItemOffset; 318 | 319 | toAppendArrayIndex = this.arrayHeadIndex.get(); 320 | 321 | // append data 322 | toAppendDataPage = this.dataPageFactory.acquirePage(toAppendDataPageIndex); 323 | ByteBuffer toAppendDataPageBuffer = toAppendDataPage.getLocal(toAppendDataItemOffset); 324 | toAppendDataPageBuffer.put(data); 325 | toAppendDataPage.setDirty(true); 326 | // update to next 327 | this.headDataItemOffset += data.length; 328 | 329 | toAppendIndexPageIndex = Calculator.div(toAppendArrayIndex, INDEX_ITEMS_PER_PAGE_BITS); // shift optimization 330 | toAppendIndexPage = this.indexPageFactory.acquirePage(toAppendIndexPageIndex); 331 | int toAppendIndexItemOffset = (int) (Calculator.mul(Calculator.mod(toAppendArrayIndex, INDEX_ITEMS_PER_PAGE_BITS), INDEX_ITEM_LENGTH_BITS)); 332 | 333 | // update index 334 | ByteBuffer toAppendIndexPageBuffer = toAppendIndexPage.getLocal(toAppendIndexItemOffset); 335 | toAppendIndexPageBuffer.putLong(toAppendDataPageIndex); 336 | toAppendIndexPageBuffer.putInt(toAppendDataItemOffset); 337 | toAppendIndexPageBuffer.putInt(data.length); 338 | long currentTime = System.currentTimeMillis(); 339 | toAppendIndexPageBuffer.putLong(currentTime); 340 | toAppendIndexPage.setDirty(true); 341 | 342 | // advance the head 343 | this.arrayHeadIndex.incrementAndGet(); 344 | 345 | // update meta data 346 | IMappedPage metaDataPage = this.metaPageFactory.acquirePage(META_DATA_PAGE_INDEX); 347 | ByteBuffer metaDataBuf = metaDataPage.getLocal(0); 348 | metaDataBuf.putLong(this.arrayHeadIndex.get()); 349 | metaDataBuf.putLong(this.arrayTailIndex.get()); 350 | metaDataPage.setDirty(true); 351 | 352 | } finally { 353 | 354 | appendLock.unlock(); 355 | 356 | if (toAppendDataPage != null) { 357 | this.dataPageFactory.releasePage(toAppendDataPageIndex); 358 | } 359 | if (toAppendIndexPage != null) { 360 | this.indexPageFactory.releasePage(toAppendIndexPageIndex); 361 | } 362 | } 363 | 364 | return toAppendArrayIndex; 365 | 366 | } finally { 367 | arrayReadLock.unlock(); 368 | } 369 | } 370 | 371 | @Override 372 | public void flush() { 373 | try { 374 | arrayReadLock.lock(); 375 | 376 | // try { 377 | // appendLock.lock(); // make flush and append mutually exclusive 378 | 379 | this.metaPageFactory.flush(); 380 | this.indexPageFactory.flush(); 381 | this.dataPageFactory.flush(); 382 | 383 | // } finally { 384 | // appendLock.unlock(); 385 | // } 386 | 387 | } finally { 388 | arrayReadLock.unlock(); 389 | } 390 | 391 | } 392 | 393 | public byte[] get(long index) throws IOException { 394 | try { 395 | arrayReadLock.lock(); 396 | validateIndex(index); 397 | 398 | IMappedPage dataPage = null; 399 | long dataPageIndex = -1L; 400 | try { 401 | ByteBuffer indexItemBuffer = this.getIndexItemBuffer(index); 402 | dataPageIndex = indexItemBuffer.getLong(); 403 | int dataItemOffset = indexItemBuffer.getInt(); 404 | int dataItemLength = indexItemBuffer.getInt(); 405 | dataPage = this.dataPageFactory.acquirePage(dataPageIndex); 406 | byte[] data = dataPage.getLocal(dataItemOffset, dataItemLength); 407 | return data; 408 | } finally { 409 | if (dataPage != null) { 410 | this.dataPageFactory.releasePage(dataPageIndex); 411 | } 412 | } 413 | } finally { 414 | arrayReadLock.unlock(); 415 | } 416 | } 417 | 418 | public long getTimestamp(long index) throws IOException { 419 | try { 420 | arrayReadLock.lock(); 421 | validateIndex(index); 422 | 423 | ByteBuffer indexItemBuffer = this.getIndexItemBuffer(index); 424 | // position to the timestamp 425 | int position = indexItemBuffer.position(); 426 | indexItemBuffer.position(position + INDEX_ITEM_DATA_ITEM_TIMESTAMP_OFFSET); 427 | long ts = indexItemBuffer.getLong(); 428 | return ts; 429 | } finally { 430 | arrayReadLock.unlock(); 431 | } 432 | } 433 | 434 | ByteBuffer getIndexItemBuffer(long index) throws IOException { 435 | 436 | IMappedPage indexPage = null; 437 | long indexPageIndex = -1L; 438 | try { 439 | indexPageIndex = Calculator.div(index, INDEX_ITEMS_PER_PAGE_BITS); // shift optimization 440 | indexPage = this.indexPageFactory.acquirePage(indexPageIndex); 441 | int indexItemOffset = (int) (Calculator.mul(Calculator.mod(index, INDEX_ITEMS_PER_PAGE_BITS), INDEX_ITEM_LENGTH_BITS)); 442 | 443 | ByteBuffer indexItemBuffer = indexPage.getLocal(indexItemOffset); 444 | return indexItemBuffer; 445 | } finally { 446 | if (indexPage != null) { 447 | this.indexPageFactory.releasePage(indexPageIndex); 448 | } 449 | } 450 | } 451 | 452 | void validateIndex(long index) { 453 | if (this.arrayTailIndex.get() <= this.arrayHeadIndex.get()) { 454 | if (index < this.arrayTailIndex.get() || index >= this.arrayHeadIndex.get()) { 455 | throw new IndexOutOfBoundsException(); 456 | } 457 | } else { 458 | if (index < this.arrayTailIndex.get() && index >= this.arrayHeadIndex.get()) { 459 | throw new IndexOutOfBoundsException(); 460 | } 461 | } 462 | } 463 | 464 | public long size() { 465 | try { 466 | arrayReadLock.lock(); 467 | if (this.arrayTailIndex.get() <= this.arrayHeadIndex.get()) { 468 | return (this.arrayHeadIndex.get() - this.arrayTailIndex.get()); 469 | } else { 470 | return Long.MAX_VALUE - this.arrayTailIndex.get() + 1 + this.arrayHeadIndex.get(); 471 | } 472 | } finally { 473 | arrayReadLock.unlock(); 474 | } 475 | } 476 | 477 | public long getHeadIndex() { 478 | try { 479 | arrayReadLock.lock(); 480 | return arrayHeadIndex.get(); 481 | } finally { 482 | arrayReadLock.unlock(); 483 | } 484 | } 485 | 486 | public long getTailIndex() { 487 | try { 488 | arrayReadLock.lock(); 489 | return arrayTailIndex.get(); 490 | } finally { 491 | arrayReadLock.unlock(); 492 | } 493 | } 494 | 495 | @Override 496 | public boolean isEmpty() { 497 | try { 498 | arrayReadLock.lock(); 499 | return this.arrayHeadIndex.get() == this.arrayTailIndex.get(); 500 | } finally { 501 | arrayReadLock.unlock(); 502 | } 503 | } 504 | 505 | @Override 506 | public boolean isFull() { 507 | try { 508 | arrayReadLock.lock(); 509 | long currentIndex = this.arrayHeadIndex.get(); 510 | 511 | long nextIndex = currentIndex == Long.MAX_VALUE ? 0 : currentIndex + 1; 512 | return nextIndex == this.arrayTailIndex.get(); 513 | } finally { 514 | arrayReadLock.unlock(); 515 | } 516 | } 517 | 518 | @Override 519 | public void close() throws IOException { 520 | try { 521 | arrayWriteLock.lock(); 522 | if (this.metaPageFactory != null) { 523 | this.metaPageFactory.releaseCachedPages(); 524 | } 525 | if (this.indexPageFactory != null) { 526 | this.indexPageFactory.releaseCachedPages(); 527 | } 528 | if (this.dataPageFactory != null) { 529 | this.dataPageFactory.releaseCachedPages(); 530 | } 531 | } finally { 532 | arrayWriteLock.unlock(); 533 | } 534 | } 535 | 536 | @Override 537 | public int getDataPageSize() { 538 | return DATA_PAGE_SIZE; 539 | } 540 | 541 | @Override 542 | public long findClosestIndex(long timestamp) throws IOException { 543 | try { 544 | arrayReadLock.lock(); 545 | long closestIndex = NOT_FOUND; 546 | long tailIndex = this.arrayTailIndex.get(); 547 | long headIndex = this.arrayHeadIndex.get(); 548 | if (tailIndex == headIndex) return closestIndex; // empty 549 | long lastIndex = headIndex - 1; 550 | if (lastIndex < 0) { 551 | lastIndex = Long.MAX_VALUE; 552 | } 553 | if (tailIndex < lastIndex) { 554 | closestIndex = closestBinarySearch(tailIndex, lastIndex, timestamp); 555 | } else { 556 | long lowPartClosestIndex = closestBinarySearch(0L, lastIndex, timestamp); 557 | long highPartClosetIndex = closestBinarySearch(tailIndex, Long.MAX_VALUE, timestamp); 558 | 559 | long lowPartTimestamp = this.getTimestamp(lowPartClosestIndex); 560 | long highPartTimestamp = this.getTimestamp(highPartClosetIndex); 561 | 562 | closestIndex = Math.abs(timestamp - lowPartTimestamp) < Math.abs(timestamp - highPartTimestamp) 563 | ? lowPartClosestIndex : highPartClosetIndex; 564 | } 565 | 566 | return closestIndex; 567 | } finally { 568 | arrayReadLock.unlock(); 569 | } 570 | } 571 | 572 | private long closestBinarySearch(long low, long high, long timestamp) throws IOException { 573 | long mid; 574 | long sum = low + high; 575 | if (sum < 0) { // overflow 576 | BigInteger bigSum = BigInteger.valueOf(low); 577 | bigSum = bigSum.add(BigInteger.valueOf(high)); 578 | mid = bigSum.shiftRight(1).longValue(); 579 | } else { 580 | mid = sum / 2; 581 | } 582 | 583 | long midTimestamp = this.getTimestamp(mid); 584 | 585 | if (midTimestamp < timestamp) { 586 | long nextLow = mid + 1; 587 | if (nextLow >= high) { 588 | return high; 589 | } 590 | return closestBinarySearch(nextLow, high, timestamp); 591 | } else if (midTimestamp > timestamp) { 592 | long nextHigh = mid - 1; 593 | if (nextHigh <= low) { 594 | return low; 595 | } 596 | return closestBinarySearch(low, nextHigh, timestamp); 597 | } else { 598 | return mid; 599 | } 600 | } 601 | 602 | @Override 603 | public long getBackFileSize() throws IOException { 604 | try { 605 | arrayReadLock.lock(); 606 | 607 | return this._getBackFileSize(); 608 | 609 | } finally { 610 | arrayReadLock.unlock(); 611 | } 612 | } 613 | 614 | @Override 615 | public void limitBackFileSize(long sizeLimit) throws IOException { 616 | if (sizeLimit < INDEX_PAGE_SIZE + DATA_PAGE_SIZE) { 617 | return; // ignore, one index page + one data page are minimum for big array to work correctly 618 | } 619 | 620 | long backFileSize = this.getBackFileSize(); 621 | if (backFileSize <= sizeLimit) return; // nothing to do 622 | 623 | long toTruncateSize = backFileSize - sizeLimit; 624 | if (toTruncateSize < DATA_PAGE_SIZE) { 625 | return; // can't do anything 626 | } 627 | 628 | try { 629 | arrayWriteLock.lock(); 630 | 631 | // double check 632 | backFileSize = this._getBackFileSize(); 633 | if (backFileSize <= sizeLimit) return; // nothing to do 634 | 635 | toTruncateSize = backFileSize - sizeLimit; 636 | if (toTruncateSize < DATA_PAGE_SIZE) { 637 | return; // can't do anything 638 | } 639 | 640 | long tailIndex = this.arrayTailIndex.get(); 641 | long headIndex = this.arrayHeadIndex.get(); 642 | long totalLength = 0L; 643 | while(true) { 644 | if (tailIndex == headIndex) break; 645 | totalLength += this.getDataItemLength(tailIndex); 646 | if (totalLength > toTruncateSize) break; 647 | 648 | if (tailIndex == Long.MAX_VALUE) { 649 | tailIndex = 0; 650 | } else { 651 | tailIndex++; 652 | } 653 | if (Calculator.mod(tailIndex, INDEX_ITEMS_PER_PAGE_BITS) == 0) { // take index page into account 654 | totalLength += INDEX_PAGE_SIZE; 655 | } 656 | } 657 | this.removeBeforeIndex(tailIndex); 658 | } finally { 659 | arrayWriteLock.unlock(); 660 | } 661 | 662 | } 663 | 664 | @Override 665 | public int getItemLength(long index) throws IOException { 666 | try { 667 | arrayReadLock.lock(); 668 | validateIndex(index); 669 | 670 | return getDataItemLength(index); 671 | 672 | } finally { 673 | arrayReadLock.unlock(); 674 | } 675 | } 676 | 677 | private int getDataItemLength(long index) throws IOException { 678 | 679 | ByteBuffer indexItemBuffer = this.getIndexItemBuffer(index); 680 | // position to the data item length 681 | int position = indexItemBuffer.position(); 682 | indexItemBuffer.position(position + INDEX_ITEM_DATA_ITEM_LENGTH_OFFSET); 683 | int length = indexItemBuffer.getInt(); 684 | return length; 685 | } 686 | 687 | // inner getBackFileSize 688 | private long _getBackFileSize() throws IOException { 689 | return this.indexPageFactory.getBackPageFileSize() + this.dataPageFactory.getBackPageFileSize(); 690 | } 691 | } 692 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/BigQueueImpl.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | import java.util.concurrent.locks.Lock; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | 9 | import com.leansoft.bigqueue.page.IMappedPage; 10 | import com.leansoft.bigqueue.page.IMappedPageFactory; 11 | import com.leansoft.bigqueue.page.MappedPageFactoryImpl; 12 | 13 | 14 | /** 15 | * A big, fast and persistent queue implementation. 16 | * 17 | * Main features: 18 | * 1. FAST : close to the speed of direct memory access, both enqueue and dequeue are close to O(1) memory access. 19 | * 2. MEMORY-EFFICIENT : automatic paging & swapping algorithm, only most-recently accessed data is kept in memory. 20 | * 3. THREAD-SAFE : multiple threads can concurrently enqueue and dequeue without data corruption. 21 | * 4. PERSISTENT - all data in queue is persisted on disk, and is crash resistant. 22 | * 5. BIG(HUGE) - the total size of the queued data is only limited by the available disk space. 23 | * 24 | * 25 | * @author bulldog 26 | * 27 | */ 28 | public class BigQueueImpl implements IBigQueue { 29 | 30 | final IBigArray innerArray; 31 | 32 | // 2 ^ 3 = 8 33 | final static int QUEUE_FRONT_INDEX_ITEM_LENGTH_BITS = 3; 34 | // size in bytes of queue front index page 35 | final static int QUEUE_FRONT_INDEX_PAGE_SIZE = 1 << QUEUE_FRONT_INDEX_ITEM_LENGTH_BITS; 36 | // only use the first page 37 | static final long QUEUE_FRONT_PAGE_INDEX = 0; 38 | 39 | // folder name for queue front index page 40 | final static String QUEUE_FRONT_INDEX_PAGE_FOLDER = "front_index"; 41 | 42 | // front index of the big queue, 43 | final AtomicLong queueFrontIndex = new AtomicLong(); 44 | 45 | // factory for queue front index page management(acquire, release, cache) 46 | IMappedPageFactory queueFrontIndexPageFactory; 47 | 48 | // locks for queue front write management 49 | final Lock queueFrontWriteLock = new ReentrantLock(); 50 | 51 | /** 52 | * A big, fast and persistent queue implementation, 53 | * use default back data page size, see {@link BigArrayImpl#DEFAULT_DATA_PAGE_SIZE} 54 | * 55 | * @param queueDir the directory to store queue data 56 | * @param queueName the name of the queue, will be appended as last part of the queue directory 57 | * @throws IOException exception throws if there is any IO error during queue initialization 58 | */ 59 | public BigQueueImpl(String queueDir, String queueName) throws IOException { 60 | this(queueDir, queueName, BigArrayImpl.DEFAULT_DATA_PAGE_SIZE); 61 | } 62 | 63 | /** 64 | * A big, fast and persistent queue implementation. 65 | * 66 | * @param queueDir the directory to store queue data 67 | * @param queueName the name of the queue, will be appended as last part of the queue directory 68 | * @param pageSize the back data file size per page in bytes, see minimum allowed {@link BigArrayImpl#MINIMUM_DATA_PAGE_SIZE} 69 | * @throws IOException exception throws if there is any IO error during queue initialization 70 | */ 71 | public BigQueueImpl(String queueDir, String queueName, int pageSize) throws IOException { 72 | innerArray = new BigArrayImpl(queueDir, queueName, pageSize); 73 | 74 | // the ttl does not matter here since queue front index page is always cached 75 | this.queueFrontIndexPageFactory = new MappedPageFactoryImpl(QUEUE_FRONT_INDEX_PAGE_SIZE, 76 | ((BigArrayImpl)innerArray).getArrayDirectory() + QUEUE_FRONT_INDEX_PAGE_FOLDER, 77 | 10 * 1000/*does not matter*/); 78 | IMappedPage queueFrontIndexPage = this.queueFrontIndexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX); 79 | 80 | ByteBuffer queueFrontIndexBuffer = queueFrontIndexPage.getLocal(0); 81 | long front = queueFrontIndexBuffer.getLong(); 82 | queueFrontIndex.set(front); 83 | } 84 | 85 | @Override 86 | public boolean isEmpty() { 87 | return this.queueFrontIndex.get() == this.innerArray.getHeadIndex(); 88 | } 89 | 90 | @Override 91 | public void enqueue(byte[] data) throws IOException { 92 | this.innerArray.append(data); 93 | } 94 | 95 | @Override 96 | public byte[] dequeue() throws IOException { 97 | long queueFrontIndex = -1L; 98 | try { 99 | queueFrontWriteLock.lock(); 100 | if (this.isEmpty()) { 101 | return null; 102 | } 103 | queueFrontIndex = this.queueFrontIndex.get(); 104 | byte[] data = this.innerArray.get(queueFrontIndex); 105 | long nextQueueFrontIndex = queueFrontIndex; 106 | if (nextQueueFrontIndex == Long.MAX_VALUE) { 107 | nextQueueFrontIndex = 0L; // wrap 108 | } else { 109 | nextQueueFrontIndex++; 110 | } 111 | this.queueFrontIndex.set(nextQueueFrontIndex); 112 | // persist the queue front 113 | IMappedPage queueFrontIndexPage = this.queueFrontIndexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX); 114 | ByteBuffer queueFrontIndexBuffer = queueFrontIndexPage.getLocal(0); 115 | queueFrontIndexBuffer.putLong(nextQueueFrontIndex); 116 | queueFrontIndexPage.setDirty(true); 117 | return data; 118 | } finally { 119 | queueFrontWriteLock.unlock(); 120 | } 121 | 122 | } 123 | 124 | @Override 125 | public void removeAll() throws IOException { 126 | try { 127 | queueFrontWriteLock.lock(); 128 | this.innerArray.removeAll(); 129 | this.queueFrontIndex.set(0L); 130 | IMappedPage queueFrontIndexPage = this.queueFrontIndexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX); 131 | ByteBuffer queueFrontIndexBuffer = queueFrontIndexPage.getLocal(0); 132 | queueFrontIndexBuffer.putLong(0L); 133 | queueFrontIndexPage.setDirty(true); 134 | } finally { 135 | queueFrontWriteLock.unlock(); 136 | } 137 | } 138 | 139 | @Override 140 | public byte[] peek() throws IOException { 141 | if (this.isEmpty()) { 142 | return null; 143 | } 144 | byte[] data = this.innerArray.get(this.queueFrontIndex.get()); 145 | return data; 146 | } 147 | 148 | /** 149 | * apply an implementation of a ItemIterator interface for each queue item 150 | * 151 | * @param iterator 152 | * @throws IOException 153 | */ 154 | @Override 155 | public void applyForEach(ItemIterator iterator) throws IOException { 156 | try { 157 | queueFrontWriteLock.lock(); 158 | if (this.isEmpty()) { 159 | return; 160 | } 161 | 162 | long index = this.queueFrontIndex.get(); 163 | for (long i=index; i queueFrontMap = new ConcurrentHashMap(); 48 | 49 | /** 50 | * A big, fast and persistent queue implementation with fandout support. 51 | * 52 | * @param queueDir the directory to store queue data 53 | * @param queueName the name of the queue, will be appended as last part of the queue directory 54 | * @param pageSize the back data file size per page in bytes, see minimum allowed {@link BigArrayImpl#MINIMUM_DATA_PAGE_SIZE} 55 | * @throws IOException exception throws if there is any IO error during queue initialization 56 | */ 57 | public FanOutQueueImpl(String queueDir, String queueName, int pageSize) 58 | throws IOException { 59 | innerArray = new BigArrayImpl(queueDir, queueName, pageSize); 60 | } 61 | 62 | /** 63 | * A big, fast and persistent queue implementation with fanout support, 64 | * use default back data page size, see {@link BigArrayImpl#DEFAULT_DATA_PAGE_SIZE} 65 | * 66 | * @param queueDir the directory to store queue data 67 | * @param queueName the name of the queue, will be appended as last part of the queue directory 68 | * @throws IOException exception throws if there is any IO error during queue initialization 69 | */ 70 | public FanOutQueueImpl(String queueDir, String queueName) throws IOException { 71 | this(queueDir, queueName, BigArrayImpl.DEFAULT_DATA_PAGE_SIZE); 72 | } 73 | 74 | QueueFront getQueueFront(String fanoutId) throws IOException { 75 | QueueFront qf = this.queueFrontMap.get(fanoutId); 76 | if (qf == null) { // not in cache, need to create one 77 | qf = new QueueFront(fanoutId); 78 | QueueFront found = this.queueFrontMap.putIfAbsent(fanoutId, qf); 79 | if (found != null) { 80 | qf.indexPageFactory.releaseCachedPages(); 81 | qf = found; 82 | } 83 | } 84 | 85 | return qf; 86 | } 87 | 88 | @Override 89 | public boolean isEmpty(String fanoutId) throws IOException { 90 | try { 91 | this.innerArray.arrayReadLock.lock(); 92 | 93 | QueueFront qf = this.getQueueFront(fanoutId); 94 | return qf.index.get() == innerArray.getHeadIndex(); 95 | 96 | } finally { 97 | this.innerArray.arrayReadLock.unlock(); 98 | } 99 | } 100 | 101 | @Override 102 | public boolean isEmpty() { 103 | return this.innerArray.isEmpty(); 104 | } 105 | 106 | @Override 107 | public long enqueue(byte[] data) throws IOException { 108 | return innerArray.append(data); 109 | } 110 | 111 | @Override 112 | public byte[] dequeue(String fanoutId) throws IOException { 113 | try { 114 | this.innerArray.arrayReadLock.lock(); 115 | 116 | QueueFront qf = this.getQueueFront(fanoutId); 117 | try { 118 | qf.writeLock.lock(); 119 | 120 | if (qf.index.get() == innerArray.arrayHeadIndex.get()) { 121 | return null; // empty 122 | } 123 | 124 | byte[] data = innerArray.get(qf.index.get()); 125 | qf.incrementIndex(); 126 | 127 | return data; 128 | } catch (IndexOutOfBoundsException ex) { 129 | ex.printStackTrace(); 130 | qf.resetIndex(); // maybe the back array has been truncated to limit size 131 | 132 | byte[] data = innerArray.get(qf.index.get()); 133 | qf.incrementIndex(); 134 | 135 | return data; 136 | 137 | } finally { 138 | qf.writeLock.unlock(); 139 | } 140 | 141 | } finally { 142 | this.innerArray.arrayReadLock.unlock(); 143 | } 144 | } 145 | 146 | @Override 147 | public byte[] peek(String fanoutId) throws IOException { 148 | try { 149 | this.innerArray.arrayReadLock.lock(); 150 | 151 | QueueFront qf = this.getQueueFront(fanoutId); 152 | if (qf.index.get() == innerArray.getHeadIndex()) { 153 | return null; // empty 154 | } 155 | 156 | return innerArray.get(qf.index.get()); 157 | 158 | } finally { 159 | this.innerArray.arrayReadLock.unlock(); 160 | } 161 | } 162 | 163 | @Override 164 | public int peekLength(String fanoutId) throws IOException { 165 | try { 166 | this.innerArray.arrayReadLock.lock(); 167 | 168 | QueueFront qf = this.getQueueFront(fanoutId); 169 | if (qf.index.get() == innerArray.getHeadIndex()) { 170 | return -1; // empty 171 | } 172 | return innerArray.getItemLength(qf.index.get()); 173 | 174 | } finally { 175 | this.innerArray.arrayReadLock.unlock(); 176 | } 177 | } 178 | 179 | @Override 180 | public long peekTimestamp(String fanoutId) throws IOException { 181 | try { 182 | this.innerArray.arrayReadLock.lock(); 183 | 184 | QueueFront qf = this.getQueueFront(fanoutId); 185 | if (qf.index.get() == innerArray.getHeadIndex()) { 186 | return -1; // empty 187 | } 188 | return innerArray.getTimestamp(qf.index.get()); 189 | 190 | } finally { 191 | this.innerArray.arrayReadLock.unlock(); 192 | } 193 | } 194 | 195 | 196 | @Override 197 | public byte[] get(long index) throws IOException { 198 | return this.innerArray.get(index); 199 | } 200 | 201 | @Override 202 | public int getLength(long index) throws IOException { 203 | return this.innerArray.getItemLength(index); 204 | } 205 | 206 | @Override 207 | public long getTimestamp(long index) throws IOException { 208 | return this.innerArray.getTimestamp(index); 209 | } 210 | 211 | @Override 212 | public void removeBefore(long timestamp) throws IOException { 213 | try { 214 | this.innerArray.arrayWriteLock.lock(); 215 | 216 | this.innerArray.removeBefore(timestamp); 217 | for(QueueFront qf : this.queueFrontMap.values()) { 218 | try { 219 | qf.writeLock.lock(); 220 | qf.validateAndAdjustIndex(); 221 | } finally { 222 | qf.writeLock.unlock(); 223 | } 224 | } 225 | } finally { 226 | this.innerArray.arrayWriteLock.unlock(); 227 | } 228 | } 229 | 230 | @Override 231 | public void limitBackFileSize(long sizeLimit) throws IOException { 232 | try { 233 | this.innerArray.arrayWriteLock.lock(); 234 | 235 | this.innerArray.limitBackFileSize(sizeLimit); 236 | 237 | for(QueueFront qf : this.queueFrontMap.values()) { 238 | try { 239 | qf.writeLock.lock(); 240 | qf.validateAndAdjustIndex(); 241 | } finally { 242 | qf.writeLock.unlock(); 243 | } 244 | } 245 | 246 | } finally { 247 | this.innerArray.arrayWriteLock.unlock(); 248 | } 249 | } 250 | 251 | @Override 252 | public long getBackFileSize() throws IOException { 253 | return this.innerArray.getBackFileSize(); 254 | } 255 | 256 | @Override 257 | public long findClosestIndex(long timestamp) throws IOException { 258 | try { 259 | this.innerArray.arrayReadLock.lock(); 260 | 261 | if (timestamp == LATEST) { 262 | return this.innerArray.getHeadIndex(); 263 | } 264 | if (timestamp == EARLIEST) { 265 | return this.innerArray.getTailIndex(); 266 | } 267 | return this.innerArray.findClosestIndex(timestamp); 268 | 269 | } finally { 270 | this.innerArray.arrayReadLock.unlock(); 271 | } 272 | } 273 | 274 | @Override 275 | public void resetQueueFrontIndex(String fanoutId, long index) throws IOException { 276 | try { 277 | this.innerArray.arrayReadLock.lock(); 278 | 279 | QueueFront qf = this.getQueueFront(fanoutId); 280 | 281 | try { 282 | qf.writeLock.lock(); 283 | 284 | if (index != this.innerArray.getHeadIndex()) { // ok to set index to array head index 285 | this.innerArray.validateIndex(index); 286 | } 287 | 288 | qf.index.set(index); 289 | qf.persistIndex(); 290 | 291 | } finally { 292 | qf.writeLock.unlock(); 293 | } 294 | 295 | } finally { 296 | this.innerArray.arrayReadLock.unlock(); 297 | } 298 | } 299 | 300 | @Override 301 | public long size(String fanoutId) throws IOException { 302 | try { 303 | this.innerArray.arrayReadLock.lock(); 304 | 305 | QueueFront qf = this.getQueueFront(fanoutId); 306 | long qFront = qf.index.get(); 307 | long qRear = innerArray.getHeadIndex(); 308 | if (qFront <= qRear) { 309 | return (qRear - qFront); 310 | } else { 311 | return Long.MAX_VALUE - qFront + 1 + qRear; 312 | } 313 | 314 | } finally { 315 | this.innerArray.arrayReadLock.unlock(); 316 | } 317 | } 318 | 319 | @Override 320 | public long size() { 321 | return this.innerArray.size(); 322 | } 323 | 324 | @Override 325 | public void flush() { 326 | try { 327 | this.innerArray.arrayReadLock.lock(); 328 | 329 | for(QueueFront qf : this.queueFrontMap.values()) { 330 | try { 331 | qf.writeLock.lock(); 332 | qf.indexPageFactory.flush(); 333 | } finally { 334 | qf.writeLock.unlock(); 335 | } 336 | } 337 | innerArray.flush(); 338 | 339 | } finally { 340 | this.innerArray.arrayReadLock.unlock(); 341 | } 342 | } 343 | 344 | @Override 345 | public void close() throws IOException { 346 | try { 347 | this.innerArray.arrayWriteLock.lock(); 348 | 349 | for(QueueFront qf : this.queueFrontMap.values()) { 350 | qf.indexPageFactory.releaseCachedPages(); 351 | } 352 | 353 | innerArray.close(); 354 | } finally { 355 | this.innerArray.arrayWriteLock.unlock(); 356 | } 357 | } 358 | 359 | @Override 360 | public void removeAll() throws IOException { 361 | try { 362 | this.innerArray.arrayWriteLock.lock(); 363 | 364 | for(QueueFront qf : this.queueFrontMap.values()) { 365 | try { 366 | qf.writeLock.lock(); 367 | qf.index.set(0L); 368 | qf.persistIndex(); 369 | } finally { 370 | qf.writeLock.unlock(); 371 | } 372 | } 373 | innerArray.removeAll(); 374 | 375 | } finally { 376 | this.innerArray.arrayWriteLock.unlock(); 377 | } 378 | } 379 | 380 | // Queue front wrapper 381 | class QueueFront { 382 | 383 | // fanout index 384 | final String fanoutId; 385 | 386 | // front index of the fanout queue 387 | final AtomicLong index = new AtomicLong(); 388 | 389 | // factory for queue front index page management(acquire, release, cache) 390 | final IMappedPageFactory indexPageFactory; 391 | 392 | // lock for queue front write management 393 | final Lock writeLock = new ReentrantLock(); 394 | 395 | QueueFront(String fanoutId) throws IOException { 396 | try { 397 | FolderNameValidator.validate(fanoutId); 398 | } catch (IllegalArgumentException ex) { 399 | throw new IllegalArgumentException("invalid fanout identifier", ex); 400 | } 401 | this.fanoutId = fanoutId; 402 | // the ttl does not matter here since queue front index page is always cached 403 | this.indexPageFactory = new MappedPageFactoryImpl(QUEUE_FRONT_INDEX_PAGE_SIZE, 404 | innerArray.arrayDirectory + QUEUE_FRONT_INDEX_PAGE_FOLDER_PREFIX + fanoutId, 405 | 10 * 1000/*does not matter*/); 406 | 407 | IMappedPage indexPage = this.indexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX); 408 | 409 | ByteBuffer indexBuffer = indexPage.getLocal(0); 410 | index.set(indexBuffer.getLong()); 411 | validateAndAdjustIndex(); 412 | } 413 | 414 | void validateAndAdjustIndex() throws IOException { 415 | if (index.get() != innerArray.arrayHeadIndex.get()) { // ok that index is equal to array head index 416 | try { 417 | innerArray.validateIndex(index.get()); 418 | } catch (IndexOutOfBoundsException ex) { // maybe the back array has been truncated to limit size 419 | resetIndex(); 420 | } 421 | } 422 | } 423 | 424 | // reset queue front index to the tail of array 425 | void resetIndex() throws IOException { 426 | index.set(innerArray.arrayTailIndex.get()); 427 | 428 | this.persistIndex(); 429 | } 430 | 431 | void incrementIndex() throws IOException { 432 | long nextIndex = index.get(); 433 | if (nextIndex == Long.MAX_VALUE) { 434 | nextIndex = 0L; // wrap 435 | } else { 436 | nextIndex++; 437 | } 438 | index.set(nextIndex); 439 | 440 | this.persistIndex(); 441 | } 442 | 443 | void persistIndex() throws IOException { 444 | // persist index 445 | IMappedPage indexPage = this.indexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX); 446 | ByteBuffer indexBuffer = indexPage.getLocal(0); 447 | indexBuffer.putLong(index.get()); 448 | indexPage.setDirty(true); 449 | } 450 | } 451 | 452 | @Override 453 | public long getFrontIndex() { 454 | return this.innerArray.getTailIndex(); 455 | } 456 | 457 | @Override 458 | public long getRearIndex() { 459 | return this.innerArray.getHeadIndex(); 460 | } 461 | 462 | @Override 463 | public long getFrontIndex(String fanoutId) throws IOException { 464 | try { 465 | this.innerArray.arrayReadLock.lock(); 466 | 467 | QueueFront qf = this.getQueueFront(fanoutId); 468 | return qf.index.get(); 469 | 470 | } finally { 471 | this.innerArray.arrayReadLock.unlock(); 472 | } 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/FanOutQueueImplEx.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/src/com/leansoft/bigqueue/FanOutQueueImplEx.java -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/IBigArray.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Append Only Big Array ADT 8 | * 9 | * @author bulldog 10 | * 11 | */ 12 | public interface IBigArray extends Closeable { 13 | 14 | public static final long NOT_FOUND = -1; 15 | 16 | /** 17 | * Append the data into the head of the array 18 | * 19 | * @param data binary data to append 20 | * @return appended index 21 | * @throws IOException if there is any IO error 22 | */ 23 | long append(byte[] data) throws IOException; 24 | 25 | 26 | /** 27 | * Get the data at specific index 28 | * 29 | * @param index valid data index 30 | * @return binary data if the index is valid 31 | * @throws IOException if there is any IO error 32 | */ 33 | byte[] get(long index) throws IOException; 34 | 35 | /** 36 | * Get the timestamp of data at specific index, 37 | * 38 | * this is the timestamp when the data was appended. 39 | * 40 | * @param index valid data index 41 | * @return timestamp when the data was appended 42 | * @throws IOException if there is any IO error 43 | */ 44 | long getTimestamp(long index) throws IOException; 45 | 46 | /** 47 | * The total number of items has been appended into the array 48 | * 49 | * @return total number 50 | */ 51 | long size(); 52 | 53 | 54 | /** 55 | * Get the back data file size per page. 56 | * 57 | * @return size per page 58 | */ 59 | int getDataPageSize(); 60 | 61 | /** 62 | * The head of the array. 63 | * 64 | * This is the next to append index, the index of the last appended data 65 | * is [headIndex - 1] if the array is not empty. 66 | * 67 | * @return an index 68 | */ 69 | long getHeadIndex(); 70 | 71 | /** 72 | * The tail of the array. 73 | * 74 | * The is the index of the first appended data 75 | * 76 | * @return an index 77 | */ 78 | long getTailIndex(); 79 | 80 | /** 81 | * Check if the array is empty or not 82 | * 83 | * @return true if empty false otherwise 84 | */ 85 | boolean isEmpty(); 86 | 87 | /** 88 | * Check if the ring space of java long type has all been used up. 89 | * 90 | * can always assume false, if true, the world is end:) 91 | * 92 | * @return array full or not 93 | */ 94 | boolean isFull(); 95 | 96 | /** 97 | * Remove all data in this array, this will empty the array and delete all back page files. 98 | * 99 | */ 100 | void removeAll() throws IOException; 101 | 102 | /** 103 | * Remove all data before specific index, this will advance the array tail to index and 104 | * delete back page files before index. 105 | * 106 | * @param index an index 107 | * @throws IOException exception thrown if there was any IO error during the removal operation 108 | */ 109 | void removeBeforeIndex(long index) throws IOException; 110 | 111 | /** 112 | * Remove all data before specific timestamp, this will advance the array tail and delete back page files 113 | * accordingly. 114 | * 115 | * @param timestamp a timestamp 116 | * @throws IOException exception thrown if there was any IO error during the removal operation 117 | */ 118 | void removeBefore(long timestamp) throws IOException; 119 | 120 | /** 121 | * Force to persist newly appended data, 122 | * 123 | * normally, you don't need to flush explicitly since: 124 | * 1.) BigArray will automatically flush a cached page when it is replaced out, 125 | * 2.) BigArray uses memory mapped file technology internally, and the OS will flush the changes even your process crashes, 126 | * 127 | * call this periodically only if you need transactional reliability and you are aware of the cost to performance. 128 | */ 129 | void flush(); 130 | 131 | /** 132 | * Find an index closest to the specific timestamp when the corresponding item was appended 133 | * 134 | * @param timestamp when the corresponding item was appended 135 | * @return an index 136 | * @throws IOException exception thrown if there was any IO error during the getClosestIndex operation 137 | */ 138 | long findClosestIndex(long timestamp) throws IOException; 139 | 140 | 141 | /** 142 | * Get total size of back files(index and data files) of the big array 143 | * 144 | * @return total size of back files 145 | * @throws IOException exception thrown if there was any IO error during the getBackFileSize operation 146 | */ 147 | long getBackFileSize() throws IOException; 148 | 149 | /** 150 | * limit the back file size, truncate back file and advance array tail index accordingly, 151 | * Note, this is a best effort call, exact size limit can't be guaranteed 152 | * 153 | * @param sizeLimit the size to limit 154 | * @throws IOException exception thrown if there was any IO error during the limitBackFileSize operation 155 | */ 156 | void limitBackFileSize(long sizeLimit) throws IOException; 157 | 158 | 159 | /** 160 | * Get the data item length at specific index 161 | * 162 | * @param index valid data index 163 | * @return the length of binary data if the index is valid 164 | * @throws IOException if there is any IO error 165 | */ 166 | int getItemLength(long index) throws IOException; 167 | } 168 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/IBigQueue.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | /** 7 | * Queue ADT 8 | * 9 | * @author bulldog 10 | * 11 | */ 12 | public interface IBigQueue extends Closeable { 13 | 14 | /** 15 | * Determines whether a queue is empty 16 | * 17 | * @return ture if empty, false otherwise 18 | */ 19 | public boolean isEmpty(); 20 | 21 | /** 22 | * Adds an item at the back of a queue 23 | * 24 | * @param data to be enqueued data 25 | * @throws IOException exception throws if there is any IO error during enqueue operation. 26 | */ 27 | public void enqueue(byte[] data) throws IOException; 28 | 29 | /** 30 | * Retrieves and removes the front of a queue 31 | * 32 | * @return data at the front of a queue 33 | * @throws IOException exception throws if there is any IO error during dequeue operation. 34 | */ 35 | public byte[] dequeue() throws IOException; 36 | 37 | /** 38 | * Removes all items of a queue, this will empty the queue and delete all back data files. 39 | * 40 | * @throws IOException exception throws if there is any IO error during dequeue operation. 41 | */ 42 | public void removeAll() throws IOException; 43 | 44 | /** 45 | * Retrieves the item at the front of a queue 46 | * 47 | * @return data at the front of a queue 48 | * @throws IOException exception throws if there is any IO error during peek operation. 49 | */ 50 | public byte[] peek() throws IOException; 51 | 52 | /** 53 | * apply an implementation of a ItemIterator interface for each queue item 54 | * 55 | * @param iterator 56 | * @throws IOException 57 | */ 58 | public void applyForEach(ItemIterator iterator) throws IOException; 59 | 60 | /** 61 | * Delete all used data files to free disk space. 62 | * 63 | * BigQueue will persist enqueued data in disk files, these data files will remain even after 64 | * the data in them has been dequeued later, so your application is responsible to periodically call 65 | * this method to delete all used data files and free disk space. 66 | * 67 | * @throws IOException exception throws if there is any IO error during gc operation. 68 | */ 69 | public void gc() throws IOException; 70 | 71 | /** 72 | * Force to persist current state of the queue, 73 | * 74 | * normally, you don't need to flush explicitly since: 75 | * 1.) BigQueue will automatically flush a cached page when it is replaced out, 76 | * 2.) BigQueue uses memory mapped file technology internally, and the OS will flush the changes even your process crashes, 77 | * 78 | * call this periodically only if you need transactional reliability and you are aware of the cost to performance. 79 | */ 80 | public void flush(); 81 | 82 | /** 83 | * Total number of items available in the queue. 84 | * @return total number 85 | */ 86 | public long size(); 87 | 88 | /** 89 | * Item iterator interface 90 | */ 91 | public static interface ItemIterator { 92 | /** 93 | * Method to be executed for each queue item 94 | * 95 | * @param item queue item 96 | * @throws IOException 97 | */ 98 | public void forEach(byte[] item) throws IOException; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/IFanOutQueue.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | /** 7 | * FanOut queue ADT 8 | * 9 | * @author bulldog 10 | * 11 | */ 12 | public interface IFanOutQueue extends Closeable { 13 | 14 | /** 15 | * Constant represents earliest timestamp 16 | */ 17 | public static final long EARLIEST = -1; 18 | /** 19 | * Constant represents latest timestamp 20 | */ 21 | public static final long LATEST = -2; 22 | 23 | /** 24 | * Determines whether a fan out queue is empty 25 | * 26 | * @param fanoutId the fanout identifier 27 | * @return true if empty, false otherwise 28 | */ 29 | public boolean isEmpty(String fanoutId) throws IOException; 30 | 31 | /** 32 | * Determines whether the queue is empty 33 | * 34 | * @return true if empty, false otherwise 35 | */ 36 | public boolean isEmpty(); 37 | 38 | /** 39 | * Adds an item at the back of the queue 40 | * 41 | * @param data to be enqueued data 42 | * @return index where the item was appended 43 | * @throws IOException exception throws if there is any IO error during enqueue operation. 44 | */ 45 | public long enqueue(byte[] data) throws IOException; 46 | 47 | /** 48 | * Retrieves and removes the front of a fan out queue 49 | * 50 | * @param fanoutId the fanout identifier 51 | * @return data at the front of a queue 52 | * @throws IOException exception throws if there is any IO error during dequeue operation. 53 | */ 54 | public byte[] dequeue(String fanoutId) throws IOException; 55 | 56 | /** 57 | * Peek the item at the front of a fanout queue, without removing it from the queue 58 | * 59 | * @param fanoutId the fanout identifier 60 | * @return data at the front of a queue 61 | * @throws IOException exception throws if there is any IO error during peek operation. 62 | */ 63 | public byte[] peek(String fanoutId) throws IOException; 64 | 65 | 66 | /** 67 | * Peek the length of the item at the front of a fan out queue 68 | * 69 | * @param fanoutId the fanout identifier 70 | * @return data at the front of a queue 71 | * @throws IOException exception throws if there is any IO error during peek operation. 72 | */ 73 | public int peekLength(String fanoutId) throws IOException; 74 | 75 | /** 76 | * Peek the timestamp of the item at the front of a fan out queue 77 | * 78 | * @param fanoutId the fanout identifier 79 | * @return data at the front of a queue 80 | * @throws IOException exception throws if there is any IO error during peek operation. 81 | */ 82 | public long peekTimestamp(String fanoutId) throws IOException; 83 | 84 | /** 85 | * Retrieves data item at the specific index of the queue 86 | * 87 | * @param index data item index 88 | * @return data at index 89 | * @throws IOException exception throws if there is any IO error during fetch operation. 90 | */ 91 | public byte[] get(long index) throws IOException; 92 | 93 | 94 | /** 95 | * Get length of data item at specific index of the queue 96 | * 97 | * @param index data item index 98 | * @return length of data item 99 | * @throws IOException exception throws if there is any IO error during fetch operation. 100 | */ 101 | public int getLength(long index) throws IOException; 102 | 103 | /** 104 | * Get timestamp of data item at specific index of the queue, this is the timestamp when corresponding item was appended into the queue. 105 | * 106 | * @param index data item index 107 | * @return timestamp of data item 108 | * @throws IOException exception throws if there is any IO error during fetch operation. 109 | */ 110 | public long getTimestamp(long index) throws IOException; 111 | 112 | /** 113 | * Total number of items remaining in the fan out queue 114 | * 115 | * @param fanoutId the fanout identifier 116 | * @return total number 117 | */ 118 | public long size(String fanoutId) throws IOException ; 119 | 120 | 121 | /** 122 | * Total number of items remaining in the queue. 123 | * 124 | * @return total number 125 | */ 126 | public long size(); 127 | 128 | /** 129 | * Force to persist current state of the queue, 130 | * 131 | * normally, you don't need to flush explicitly since: 132 | * 1.) FanOutQueue will automatically flush a cached page when it is replaced out, 133 | * 2.) FanOutQueue uses memory mapped file technology internally, and the OS will flush the changes even your process crashes, 134 | * 135 | * call this periodically only if you need transactional reliability and you are aware of the cost to performance. 136 | */ 137 | public void flush(); 138 | 139 | /** 140 | * Remove all data before specific timestamp, truncate back files and advance the queue front if necessary. 141 | * 142 | * @param timestamp a timestamp 143 | * @throws IOException exception thrown if there was any IO error during the removal operation 144 | */ 145 | void removeBefore(long timestamp) throws IOException; 146 | 147 | /** 148 | * Limit the back file size of this queue, truncate back files and advance the queue front if necessary. 149 | * 150 | * Note, this is a best effort call, exact size limit can't be guaranteed 151 | * 152 | * @param sizeLmit size limit 153 | * @throws IOException exception thrown if there was any IO error during the operation 154 | */ 155 | void limitBackFileSize(long sizeLmit) throws IOException; 156 | 157 | /** 158 | * Current total size of the back files of this queue 159 | * 160 | * @return total back file size 161 | * @throws IOException exception thrown if there was any IO error during the operation 162 | */ 163 | long getBackFileSize() throws IOException; 164 | 165 | 166 | /** 167 | * Find an index closest to the specific timestamp when the corresponding item was enqueued. 168 | * to find latest index, use {@link #LATEST} as timestamp. 169 | * to find earliest index, use {@link #EARLIEST} as timestamp. 170 | * 171 | * @param timestamp when the corresponding item was appended 172 | * @return an index 173 | * @throws IOException exception thrown during the operation 174 | */ 175 | long findClosestIndex(long timestamp) throws IOException; 176 | 177 | /** 178 | * Reset the front index of a fanout queue. 179 | * 180 | * @param fandoutId fanout identifier 181 | * @param index target index 182 | * @throws IOException exception thrown during the operation 183 | */ 184 | void resetQueueFrontIndex(String fanoutId, long index) throws IOException; 185 | 186 | /** 187 | * Removes all items of a queue, this will empty the queue and delete all back data files. 188 | * 189 | * @throws IOException exception throws if there is any IO error during dequeue operation. 190 | */ 191 | public void removeAll() throws IOException; 192 | 193 | /** 194 | * Get the queue front index, this is the earliest appended index 195 | * 196 | * @return an index 197 | */ 198 | public long getFrontIndex(); 199 | 200 | /** 201 | * Get front index of specific fanout queue 202 | * 203 | * @param fanoutId fanout identifier 204 | * @return an index 205 | */ 206 | public long getFrontIndex(String fanoutId) throws IOException; 207 | 208 | /** 209 | * Get the queue rear index, this is the next to be appended index 210 | * 211 | * @return an index 212 | */ 213 | public long getRearIndex(); 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/cache/ILRUCache.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.cache; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.Collection; 6 | 7 | /** 8 | * LRU cache ADT 9 | * 10 | * @author bulldog 11 | * 12 | * @param the key 13 | * @param the value 14 | */ 15 | public interface ILRUCache { 16 | 17 | /** 18 | * Put a keyed resource with specific ttl into the cache 19 | * 20 | * This call will increment the reference counter of the keyed resource. 21 | * 22 | * @param key the key of the cached resource 23 | * @param value the to be cached resource 24 | * @param ttlInMilliSeconds time to live in milliseconds 25 | */ 26 | public void put(final K key, final V value, final long ttlInMilliSeconds); 27 | 28 | /** 29 | * Put a keyed resource with default ttl into the cache 30 | * 31 | * This call will increment the reference counter of the keyed resource. 32 | * 33 | * @param key the key of the cached resource 34 | * @param value the to be cached resource 35 | */ 36 | public void put(final K key, final V value); 37 | 38 | 39 | /** 40 | * Get a cached resource with specific key 41 | * 42 | * This call will increment the reference counter of the keyed resource. 43 | * 44 | * @param key the key of the cached resource 45 | * @return cached resource if exists 46 | */ 47 | public V get(final K key); 48 | 49 | /** 50 | * Release the cached resource with specific key 51 | * 52 | * This call will decrement the reference counter of the keyed resource. 53 | * 54 | * @param key 55 | */ 56 | public void release(final K key); 57 | 58 | /** 59 | * Remove the resource with specific key from the cache and close it synchronously afterwards. 60 | * 61 | * @param key the key of the cached resource 62 | * @return the removed resource if exists 63 | * @throws IOException exception thrown if there is any IO error 64 | */ 65 | public V remove(final K key) throws IOException; 66 | 67 | /** 68 | * Remove all cached resource from the cache and close them asynchronously afterwards. 69 | * 70 | * @throws IOException exception thrown if there is any IO error 71 | */ 72 | public void removeAll() throws IOException; 73 | 74 | /** 75 | * The size of the cache, equals to current total number of cached resources. 76 | * 77 | * @return the size of the cache 78 | */ 79 | public int size(); 80 | 81 | /** 82 | * All values cached 83 | * @return a collection 84 | */ 85 | public Collection getValues(); 86 | } 87 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/cache/LRUCacheImpl.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.cache; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.atomic.AtomicLong; 14 | import java.util.concurrent.locks.Lock; 15 | import java.util.concurrent.locks.ReadWriteLock; 16 | import java.util.concurrent.locks.ReentrantReadWriteLock; 17 | 18 | import org.apache.log4j.Logger; 19 | 20 | /** 21 | * Simple and thread-safe LRU cache implementation, 22 | * supporting time to live and reference counting for entry. 23 | * 24 | * in current implementation, entry expiration and purge(mark&sweep) is triggered by put operation, 25 | * and resource closing after mark&sweep is done in async way. 26 | * 27 | * @author bulldog 28 | * 29 | * @param key 30 | * @param value 31 | */ 32 | public class LRUCacheImpl implements ILRUCache { 33 | 34 | private final static Logger logger = Logger.getLogger(LRUCacheImpl.class); 35 | 36 | public static final long DEFAULT_TTL = 10 * 1000; // milliseconds 37 | 38 | private final Map map; 39 | private final Map ttlMap; 40 | private final ReadWriteLock lock = new ReentrantReadWriteLock(); 41 | private final Lock readLock = lock.readLock(); 42 | private final Lock writeLock = lock.writeLock(); 43 | 44 | private static final ExecutorService executorService = Executors.newCachedThreadPool(); 45 | 46 | private final Set keysToRemove = new HashSet(); 47 | 48 | public LRUCacheImpl() { 49 | map = new HashMap(); 50 | ttlMap = new HashMap(); 51 | } 52 | 53 | public void put(K key, V value, long ttlInMilliSeconds) { 54 | Collection valuesToClose = null; 55 | try { 56 | writeLock.lock(); 57 | // trigger mark&sweep 58 | valuesToClose = markAndSweep(); 59 | if (valuesToClose != null && valuesToClose.contains(value)) { // just be cautious 60 | valuesToClose.remove(value); 61 | } 62 | map.put(key, value); 63 | TTLValue ttl = new TTLValue(System.currentTimeMillis(), ttlInMilliSeconds); 64 | ttl.refCount.incrementAndGet(); 65 | ttlMap.put(key, ttl); 66 | } finally { 67 | writeLock.unlock(); 68 | } 69 | if (valuesToClose != null && valuesToClose.size() > 0) { 70 | if (logger.isDebugEnabled()) { 71 | int size = valuesToClose.size(); 72 | logger.info("Mark&Sweep found " + size + (size > 1 ? " resources":" resource") + " to close."); 73 | } 74 | // close resource asynchronously 75 | executorService.execute(new ValueCloser(valuesToClose)); 76 | } 77 | } 78 | 79 | public void put(K key, V value) { 80 | this.put(key, value, DEFAULT_TTL); 81 | } 82 | 83 | /** 84 | * A lazy mark and sweep, 85 | * 86 | * a separate thread can also do this. 87 | */ 88 | private Collection markAndSweep() { 89 | Collection valuesToClose = null; 90 | keysToRemove.clear(); 91 | Set keys = ttlMap.keySet(); 92 | long currentTS = System.currentTimeMillis(); 93 | for(K key: keys) { 94 | TTLValue ttl = ttlMap.get(key); 95 | if (ttl.refCount.get() <= 0 && (currentTS - ttl.lastAccessedTimestamp.get()) > ttl.ttl) { // remove object with no reference and expired 96 | keysToRemove.add(key); 97 | } 98 | } 99 | 100 | if (keysToRemove.size() > 0) { 101 | valuesToClose = new HashSet(); 102 | for(K key : keysToRemove) { 103 | V v = map.remove(key); 104 | valuesToClose.add(v); 105 | ttlMap.remove(key); 106 | } 107 | } 108 | 109 | return valuesToClose; 110 | } 111 | 112 | public V get(K key) { 113 | try { 114 | readLock.lock(); 115 | TTLValue ttl = ttlMap.get(key); 116 | if (ttl != null) { 117 | // Since the resource is acquired by calling thread, 118 | // let's update last accessed timestamp and increment reference counting 119 | ttl.lastAccessedTimestamp.set(System.currentTimeMillis()); 120 | ttl.refCount.incrementAndGet(); 121 | } 122 | return map.get(key); 123 | } finally { 124 | readLock.unlock(); 125 | } 126 | } 127 | 128 | private static class TTLValue { 129 | AtomicLong lastAccessedTimestamp; // last accessed time 130 | AtomicLong refCount = new AtomicLong(0); 131 | long ttl; 132 | 133 | public TTLValue(long ts, long ttl) { 134 | this.lastAccessedTimestamp = new AtomicLong(ts); 135 | this.ttl = ttl; 136 | } 137 | } 138 | 139 | private static class ValueCloser implements Runnable { 140 | Collection valuesToClose; 141 | 142 | public ValueCloser(Collection valuesToClose) { 143 | this.valuesToClose = valuesToClose; 144 | } 145 | 146 | public void run() { 147 | int size = valuesToClose.size(); 148 | for(V v : valuesToClose) { 149 | try { 150 | if (v != null) { 151 | v.close(); 152 | } 153 | } catch (IOException e) { 154 | // close quietly 155 | } 156 | } 157 | if (logger.isDebugEnabled()) { 158 | logger.debug("ResourceCloser closed " + size + (size > 1 ? " resources.":" resource.")); 159 | } 160 | } 161 | } 162 | 163 | public void release(K key) { 164 | try { 165 | readLock.lock(); 166 | TTLValue ttl = ttlMap.get(key); 167 | if (ttl != null) { 168 | // since the resource is released by calling thread 169 | // let's decrement the reference counting 170 | ttl.refCount.decrementAndGet(); 171 | } 172 | } finally { 173 | readLock.unlock(); 174 | } 175 | } 176 | 177 | public int size() { 178 | try { 179 | readLock.lock(); 180 | return map.size(); 181 | } finally { 182 | readLock.unlock(); 183 | } 184 | } 185 | 186 | @Override 187 | public void removeAll() throws IOException { 188 | try { 189 | writeLock.lock(); 190 | 191 | Collection valuesToClose = new HashSet(); 192 | valuesToClose.addAll(map.values()); 193 | 194 | if (valuesToClose != null && valuesToClose.size() > 0) { 195 | // close resource synchronously 196 | for(V v : valuesToClose) { 197 | v.close(); 198 | } 199 | } 200 | map.clear(); 201 | ttlMap.clear(); 202 | 203 | } finally { 204 | writeLock.unlock(); 205 | } 206 | 207 | } 208 | 209 | @Override 210 | public V remove(K key) throws IOException { 211 | try { 212 | writeLock.lock(); 213 | ttlMap.remove(key); 214 | V value = map.remove(key); 215 | if (value != null) { 216 | // close synchronously 217 | value.close(); 218 | } 219 | return value; 220 | } finally { 221 | writeLock.unlock(); 222 | } 223 | 224 | } 225 | 226 | @Override 227 | public Collection getValues() { 228 | try { 229 | readLock.lock(); 230 | Collection col = new ArrayList(); 231 | for(V v : map.values()) { 232 | col.add(v); 233 | } 234 | return col; 235 | } finally { 236 | readLock.unlock(); 237 | } 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/page/IMappedPage.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.page; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | /** 6 | * Memory mapped page file ADT 7 | * 8 | * @author bulldog 9 | * 10 | */ 11 | public interface IMappedPage { 12 | 13 | /** 14 | * Get a thread local copy of the mapped page buffer 15 | * 16 | * @param position start position(relative to the start position of source mapped page buffer) of the thread local buffer 17 | * @return a byte buffer with specific position as start position. 18 | */ 19 | ByteBuffer getLocal(int position); 20 | 21 | /** 22 | * Get data from a thread local copy of the mapped page buffer 23 | * 24 | * @param position start position(relative to the start position of source mapped page buffer) of the thread local buffer 25 | * @param length the length to fetch 26 | * @return byte data 27 | */ 28 | public byte[] getLocal(int position, int length); 29 | 30 | /** 31 | * Check if this mapped page has been closed or not 32 | * 33 | * @return 34 | */ 35 | boolean isClosed(); 36 | 37 | /** 38 | * Set if the mapped page has been changed or not 39 | * 40 | * @param dirty 41 | */ 42 | void setDirty(boolean dirty); 43 | 44 | /** 45 | * The back page file name of the mapped page 46 | * 47 | * @return 48 | */ 49 | String getPageFile(); 50 | 51 | /** 52 | * The index of the mapped page 53 | * 54 | * @return the index 55 | */ 56 | long getPageIndex(); 57 | 58 | /** 59 | * Persist any changes to disk 60 | */ 61 | public void flush(); 62 | } 63 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/page/IMappedPageFactory.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.page; 2 | 3 | import java.io.IOException; 4 | import java.util.Set; 5 | 6 | /** 7 | * Memory mapped page management ADT 8 | * 9 | * @author bulldog 10 | * 11 | */ 12 | public interface IMappedPageFactory { 13 | 14 | /** 15 | * Acquire a mapped page with specific index from the factory 16 | * 17 | * @param index the index of the page 18 | * @return a mapped page 19 | * @throws IOException exception thrown if there was any IO error during the acquire operation 20 | */ 21 | IMappedPage acquirePage(long index) throws IOException; 22 | 23 | /** 24 | * Return the mapped page to the factory, 25 | * calling thread release the page to inform the factory that it has finished with the page, 26 | * so the factory get a chance to recycle the page to save memory. 27 | * 28 | * @param index the index of the page 29 | */ 30 | void releasePage(long index); 31 | 32 | /** 33 | * Current set page size, when creating pages, the factory will 34 | * only create pages with this size. 35 | * 36 | * @return an integer number 37 | */ 38 | int getPageSize(); 39 | 40 | /** 41 | * Current set page directory. 42 | * 43 | * @return 44 | */ 45 | String getPageDir(); 46 | 47 | /** 48 | * delete a mapped page with specific index in this factory, 49 | * this call will remove the page from the cache if it is cached and 50 | * delete back file. 51 | * 52 | * @param index the index of the page 53 | * @throws IOException exception thrown if there was any IO error during the delete operation. 54 | */ 55 | void deletePage(long index) throws IOException; 56 | 57 | /** 58 | * delete mapped pages with a set of specific indexes in this factory, 59 | * this call will remove the pages from the cache if they ware cached and 60 | * delete back files. 61 | * 62 | * @param indexes the indexes of the pages 63 | * @throws IOException 64 | */ 65 | void deletePages(Set indexes) throws IOException; 66 | 67 | /** 68 | * delete all mapped pages currently available in this factory, 69 | * this call will remove all pages from the cache and delete all back files. 70 | * 71 | * @throws IOException exception thrown if there was any IO error during the delete operation. 72 | */ 73 | void deleteAllPages() throws IOException; 74 | 75 | /** 76 | * remove all cached pages from the cache and close resources associated with the cached pages. 77 | * 78 | * @throws IOException exception thrown if there was any IO error during the release operation. 79 | */ 80 | void releaseCachedPages() throws IOException; 81 | 82 | /** 83 | * Get all indexes of pages with last modified timestamp before the specific timestamp. 84 | * 85 | * @param timestamp the timestamp to check 86 | * @return a set of indexes 87 | */ 88 | Set getPageIndexSetBefore(long timestamp); 89 | 90 | /** 91 | * Delete all pages with last modified timestamp before the specific timestamp. 92 | * 93 | * @param timestamp the timestamp to check 94 | * @throws IOException exception thrown if there was any IO error during the delete operation. 95 | */ 96 | void deletePagesBefore(long timestamp) throws IOException; 97 | 98 | /** 99 | * Delete all pages before the specific index 100 | * 101 | * @param pageIndex page file index to check 102 | * @throws IOException exception thrown if there was any IO error during the delete operation. 103 | */ 104 | void deletePagesBeforePageIndex(long pageIndex) throws IOException; 105 | 106 | /** 107 | * Get last modified timestamp of page file index 108 | * 109 | * @param index page index 110 | */ 111 | long getPageFileLastModifiedTime(long index); 112 | 113 | /** 114 | * Get index of a page file with last modified timestamp closest to specific timestamp. 115 | * 116 | * @param timestamp the timestamp to check 117 | * @return a page index 118 | */ 119 | long getFirstPageIndexBefore(long timestamp); 120 | 121 | /** 122 | * For test, get a list of indexes of current existing back files. 123 | * 124 | * @return a set of indexes 125 | */ 126 | Set getExistingBackFileIndexSet(); 127 | 128 | /** 129 | * For test, get current cache size 130 | * 131 | * @return an integer number 132 | */ 133 | int getCacheSize(); 134 | 135 | /** 136 | * Persist any changes in cached mapped pages 137 | */ 138 | void flush(); 139 | 140 | /** 141 | * 142 | * A set of back page file names 143 | * 144 | * @return file name set 145 | */ 146 | Set getBackPageFileSet(); 147 | 148 | 149 | /** 150 | * Total size of all page files 151 | * 152 | * @return total size 153 | */ 154 | long getBackPageFileSize(); 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/page/MappedPageFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.page; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.MappedByteBuffer; 7 | import java.nio.channels.FileChannel; 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.TreeSet; 14 | 15 | import org.apache.log4j.Logger; 16 | 17 | import com.leansoft.bigqueue.cache.ILRUCache; 18 | import com.leansoft.bigqueue.cache.LRUCacheImpl; 19 | import com.leansoft.bigqueue.utils.FileUtil; 20 | 21 | import static java.nio.channels.FileChannel.MapMode.READ_WRITE; 22 | 23 | /** 24 | * Mapped mapped page resource manager, 25 | * responsible for the creation, cache, recycle of the mapped pages. 26 | * 27 | * automatic paging and swapping algorithm is leveraged to ensure fast page fetch while 28 | * keep memory usage efficient at the same time. 29 | * 30 | * @author bulldog 31 | * 32 | */ 33 | public class MappedPageFactoryImpl implements IMappedPageFactory { 34 | 35 | private final static Logger logger = Logger.getLogger(MappedPageFactoryImpl.class); 36 | 37 | private int pageSize; 38 | private String pageDir; 39 | private File pageDirFile; 40 | private String pageFile; 41 | private long ttl; 42 | 43 | private final Object mapLock = new Object(); 44 | private final Map pageCreationLockMap = new HashMap(); 45 | 46 | public static final String PAGE_FILE_NAME = "page"; 47 | public static final String PAGE_FILE_SUFFIX = ".dat"; 48 | 49 | private ILRUCache cache; 50 | 51 | public MappedPageFactoryImpl(int pageSize, String pageDir, long cacheTTL) { 52 | this.pageSize = pageSize; 53 | this.pageDir = pageDir; 54 | this.ttl = cacheTTL; 55 | this.pageDirFile = new File(this.pageDir); 56 | if (!pageDirFile.exists()) { 57 | pageDirFile.mkdirs(); 58 | } 59 | if (!this.pageDir.endsWith(File.separator)) { 60 | this.pageDir += File.separator; 61 | } 62 | this.pageFile = this.pageDir + PAGE_FILE_NAME + "-"; 63 | this.cache = new LRUCacheImpl(); 64 | } 65 | 66 | public IMappedPage acquirePage(long index) throws IOException { 67 | MappedPageImpl mpi = cache.get(index); 68 | if (mpi == null) { // not in cache, need to create one 69 | try { 70 | Object lock = null; 71 | synchronized(mapLock) { 72 | if (!pageCreationLockMap.containsKey(index)) { 73 | pageCreationLockMap.put(index, new Object()); 74 | } 75 | lock = pageCreationLockMap.get(index); 76 | } 77 | synchronized(lock) { // only lock the creation of page index 78 | mpi = cache.get(index); // double check 79 | if (mpi == null) { 80 | RandomAccessFile raf = null; 81 | FileChannel channel = null; 82 | try { 83 | String fileName = this.getFileNameByIndex(index); 84 | raf = new RandomAccessFile(fileName, "rw"); 85 | channel = raf.getChannel(); 86 | MappedByteBuffer mbb = channel.map(READ_WRITE, 0, this.pageSize); 87 | mpi = new MappedPageImpl(mbb, fileName, index); 88 | cache.put(index, mpi, ttl); 89 | if (logger.isDebugEnabled()) { 90 | logger.debug("Mapped page for " + fileName + " was just created and cached."); 91 | } 92 | } finally { 93 | if (channel != null) channel.close(); 94 | if (raf != null) raf.close(); 95 | } 96 | } 97 | } 98 | } finally { 99 | synchronized(mapLock) { 100 | pageCreationLockMap.remove(index); 101 | } 102 | } 103 | } else { 104 | if (logger.isDebugEnabled()) { 105 | logger.debug("Hit mapped page " + mpi.getPageFile() + " in cache."); 106 | } 107 | } 108 | 109 | return mpi; 110 | } 111 | 112 | private String getFileNameByIndex(long index) { 113 | return this.pageFile + index + PAGE_FILE_SUFFIX; 114 | } 115 | 116 | 117 | public int getPageSize() { 118 | return pageSize; 119 | } 120 | 121 | public String getPageDir() { 122 | return pageDir; 123 | } 124 | 125 | public void releasePage(long index) { 126 | cache.release(index); 127 | } 128 | 129 | /** 130 | * thread unsafe, caller need synchronization 131 | */ 132 | @Override 133 | public void releaseCachedPages() throws IOException { 134 | cache.removeAll(); 135 | } 136 | 137 | /** 138 | * thread unsafe, caller need synchronization 139 | */ 140 | @Override 141 | public void deleteAllPages() throws IOException { 142 | cache.removeAll(); 143 | Set indexSet = getExistingBackFileIndexSet(); 144 | this.deletePages(indexSet); 145 | if (logger.isDebugEnabled()) { 146 | logger.debug("All page files in dir " + this.pageDir + " have been deleted."); 147 | } 148 | } 149 | 150 | /** 151 | * thread unsafe, caller need synchronization 152 | */ 153 | @Override 154 | public void deletePages(Set indexes) throws IOException { 155 | if (indexes == null) return; 156 | for(long index : indexes) { 157 | this.deletePage(index); 158 | } 159 | } 160 | 161 | /** 162 | * thread unsafe, caller need synchronization 163 | */ 164 | @Override 165 | public void deletePage(long index) throws IOException { 166 | // remove the page from cache first 167 | cache.remove(index); 168 | String fileName = this.getFileNameByIndex(index); 169 | int count = 0; 170 | int maxRound = 10; 171 | boolean deleted = false; 172 | while(count < maxRound) { 173 | try { 174 | FileUtil.deleteFile(new File(fileName)); 175 | deleted = true; 176 | break; 177 | } catch (IllegalStateException ex) { 178 | try { 179 | Thread.sleep(200); 180 | } catch (InterruptedException e) { 181 | } 182 | count++; 183 | if (logger.isDebugEnabled()) { 184 | logger.warn("fail to delete file " + fileName + ", tried round = " + count); 185 | } 186 | } 187 | } 188 | if (deleted) { 189 | logger.info("Page file " + fileName + " was just deleted."); 190 | } else { 191 | logger.warn("fail to delete file " + fileName + " after max " + maxRound + " rounds of try, you may delete it manually."); 192 | } 193 | } 194 | 195 | @Override 196 | public Set getPageIndexSetBefore(long timestamp) { 197 | Set beforeIndexSet = new HashSet(); 198 | File[] pageFiles = this.pageDirFile.listFiles(); 199 | if (pageFiles != null && pageFiles.length > 0) { 200 | for(File pageFile : pageFiles) { 201 | if (pageFile.lastModified() < timestamp) { 202 | String fileName = pageFile.getName(); 203 | if (fileName.endsWith(PAGE_FILE_SUFFIX)) { 204 | long index = this.getIndexByFileName(fileName); 205 | beforeIndexSet.add(index); 206 | } 207 | } 208 | } 209 | } 210 | return beforeIndexSet; 211 | } 212 | 213 | private long getIndexByFileName(String fileName) { 214 | int beginIndex = fileName.lastIndexOf('-'); 215 | beginIndex += 1; 216 | int endIndex = fileName.lastIndexOf(PAGE_FILE_SUFFIX); 217 | String sIndex = fileName.substring(beginIndex, endIndex); 218 | long index = Long.parseLong(sIndex); 219 | return index; 220 | } 221 | 222 | /** 223 | * thread unsafe, caller need synchronization 224 | * @throws IOException 225 | */ 226 | @Override 227 | public void deletePagesBefore(long timestamp) throws IOException { 228 | Set indexSet = this.getPageIndexSetBefore(timestamp); 229 | this.deletePages(indexSet); 230 | if (logger.isDebugEnabled()) { 231 | logger.debug("All page files in dir [" + this.pageDir + "], before [" + timestamp + "] have been deleted."); 232 | } 233 | } 234 | 235 | @Override 236 | public void deletePagesBeforePageIndex(long pageIndex) throws IOException { 237 | Set indexSet = this.getExistingBackFileIndexSet(); 238 | for (Long index : indexSet) { 239 | if (index < pageIndex) { 240 | this.deletePage(index); 241 | } 242 | } 243 | } 244 | 245 | 246 | @Override 247 | public Set getExistingBackFileIndexSet() { 248 | Set indexSet = new HashSet(); 249 | File[] pageFiles = this.pageDirFile.listFiles(); 250 | if (pageFiles != null && pageFiles.length > 0) { 251 | for(File pageFile : pageFiles) { 252 | String fileName = pageFile.getName(); 253 | if (fileName.endsWith(PAGE_FILE_SUFFIX)) { 254 | long index = this.getIndexByFileName(fileName); 255 | indexSet.add(index); 256 | } 257 | } 258 | } 259 | return indexSet; 260 | } 261 | 262 | @Override 263 | public int getCacheSize() { 264 | return cache.size(); 265 | } 266 | 267 | // for testing 268 | int getLockMapSize() { 269 | return this.pageCreationLockMap.size(); 270 | } 271 | 272 | @Override 273 | public long getPageFileLastModifiedTime(long index) { 274 | String pageFileName = this.getFileNameByIndex(index); 275 | File pageFile = new File(pageFileName); 276 | if (!pageFile.exists()) { 277 | return -1L; 278 | } 279 | return pageFile.lastModified(); 280 | } 281 | 282 | @Override 283 | public long getFirstPageIndexBefore(long timestamp) { 284 | Set beforeIndexSet = getPageIndexSetBefore(timestamp); 285 | if (beforeIndexSet.size() == 0) return -1L; 286 | TreeSet sortedIndexSet = new TreeSet(beforeIndexSet); 287 | Long largestIndex = sortedIndexSet.last(); 288 | if (largestIndex != Long.MAX_VALUE) { // no wrap, just return the largest 289 | return largestIndex; 290 | } else { // wrapped case 291 | Long next = 0L; 292 | while(sortedIndexSet.contains(next)) { 293 | next++; 294 | } 295 | if (next == 0L) { 296 | return Long.MAX_VALUE; 297 | } else { 298 | return --next; 299 | } 300 | } 301 | } 302 | 303 | /** 304 | * thread unsafe, caller need synchronization 305 | */ 306 | @Override 307 | public void flush() { 308 | Collection cachedPages = cache.getValues(); 309 | for(IMappedPage mappedPage : cachedPages) { 310 | mappedPage.flush(); 311 | } 312 | } 313 | 314 | @Override 315 | public Set getBackPageFileSet() { 316 | Set fileSet = new HashSet(); 317 | File[] pageFiles = this.pageDirFile.listFiles(); 318 | if (pageFiles != null && pageFiles.length > 0) { 319 | for(File pageFile : pageFiles) { 320 | String fileName = pageFile.getName(); 321 | if (fileName.endsWith(PAGE_FILE_SUFFIX)) { 322 | fileSet.add(fileName); 323 | } 324 | } 325 | } 326 | return fileSet; 327 | } 328 | 329 | @Override 330 | public long getBackPageFileSize() { 331 | long totalSize = 0L; 332 | File[] pageFiles = this.pageDirFile.listFiles(); 333 | if (pageFiles != null && pageFiles.length > 0) { 334 | for(File pageFile : pageFiles) { 335 | String fileName = pageFile.getName(); 336 | if (fileName.endsWith(PAGE_FILE_SUFFIX)) { 337 | totalSize += pageFile.length(); 338 | } 339 | } 340 | } 341 | return totalSize; 342 | } 343 | 344 | } 345 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/page/MappedPageImpl.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.page; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.nio.Buffer; 8 | import java.nio.ByteBuffer; 9 | import java.nio.MappedByteBuffer; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | import sun.misc.Unsafe; 14 | 15 | public class MappedPageImpl implements IMappedPage, Closeable { 16 | 17 | private final static Logger logger = Logger.getLogger(MappedPageImpl.class); 18 | 19 | private ThreadLocalByteBuffer threadLocalBuffer; 20 | private volatile boolean dirty = false; 21 | private volatile boolean closed = false; 22 | private String pageFile; 23 | private long index; 24 | 25 | public MappedPageImpl(MappedByteBuffer mbb, String pageFile, long index) { 26 | this.threadLocalBuffer = new ThreadLocalByteBuffer(mbb); 27 | this.pageFile = pageFile; 28 | this.index = index; 29 | } 30 | 31 | public void close() throws IOException { 32 | synchronized (this) { 33 | if (closed) 34 | return; 35 | 36 | flush(); 37 | 38 | MappedByteBuffer srcBuf = (MappedByteBuffer) threadLocalBuffer.getSourceBuffer(); 39 | unmap(srcBuf); 40 | 41 | this.threadLocalBuffer = null; // hint GC 42 | 43 | closed = true; 44 | if (logger.isDebugEnabled()) { 45 | logger.debug("Mapped page for " + this.pageFile + " was just unmapped and closed."); 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public void setDirty(boolean dirty) { 52 | this.dirty = dirty; 53 | } 54 | 55 | @Override 56 | public void flush() { 57 | synchronized (this) { 58 | if (closed) 59 | return; 60 | if (dirty) { 61 | MappedByteBuffer srcBuf = (MappedByteBuffer) threadLocalBuffer.getSourceBuffer(); 62 | srcBuf.force(); // flush the changes 63 | dirty = false; 64 | if (logger.isDebugEnabled()) { 65 | logger.debug("Mapped page for " + this.pageFile + " was just flushed."); 66 | } 67 | } 68 | } 69 | } 70 | 71 | public byte[] getLocal(int position, int length) { 72 | ByteBuffer buf = this.getLocal(position); 73 | byte[] data = new byte[length]; 74 | buf.get(data); 75 | return data; 76 | } 77 | 78 | @Override 79 | public ByteBuffer getLocal(int position) { 80 | ByteBuffer buf = this.threadLocalBuffer.get(); 81 | ((Buffer) buf).position(position); 82 | return buf; 83 | } 84 | 85 | private static void unmap(MappedByteBuffer buffer) { 86 | Cleaner.clean(buffer); 87 | } 88 | 89 | /** 90 | * Helper class allowing to clean direct buffers. 91 | */ 92 | private static class Cleaner { 93 | //for JDK8 94 | public static final boolean CLEAN_SUPPORTED; 95 | private static final Method directBufferCleaner; 96 | private static final Method directBufferCleanerClean; 97 | static { 98 | Method directBufferCleanerX = null; 99 | Method directBufferCleanerCleanX = null; 100 | boolean v; 101 | try { 102 | directBufferCleanerX = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner"); 103 | directBufferCleanerX.setAccessible(true); 104 | directBufferCleanerCleanX = Class.forName("sun.misc.Cleaner").getMethod("clean"); 105 | directBufferCleanerCleanX.setAccessible(true); 106 | v = true; 107 | } catch (Exception e) { 108 | v = false; 109 | } 110 | CLEAN_SUPPORTED = v; 111 | directBufferCleaner = directBufferCleanerX; 112 | directBufferCleanerClean = directBufferCleanerCleanX; 113 | } 114 | 115 | //for JDK11 116 | private static Unsafe unsafe; 117 | static { 118 | try { 119 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 120 | f.setAccessible(true); 121 | unsafe = (Unsafe) f.get(null); 122 | 123 | Unsafe.class.getDeclaredMethod("invokeCleaner", ByteBuffer.class); 124 | } catch (Exception e) { 125 | unsafe = null; 126 | logger.warn("Unsafe Not support " + e.getMessage()); 127 | } 128 | } 129 | 130 | public static void clean(ByteBuffer buffer) { 131 | if (buffer == null) { 132 | return; 133 | } 134 | 135 | if (buffer.isDirect()) { 136 | if (unsafe != null) { //JDK 11 137 | unsafe.invokeCleaner(buffer); 138 | } else { //JDK 8 139 | if (CLEAN_SUPPORTED && buffer.isDirect()) { 140 | try { 141 | Object cleaner = directBufferCleaner.invoke(buffer); 142 | directBufferCleanerClean.invoke(cleaner); 143 | } catch (Exception e) { 144 | // silently ignore exception 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | private static class ThreadLocalByteBuffer extends ThreadLocal { 153 | private ByteBuffer _src; 154 | 155 | public ThreadLocalByteBuffer(ByteBuffer src) { 156 | _src = src; 157 | } 158 | 159 | public ByteBuffer getSourceBuffer() { 160 | return _src; 161 | } 162 | 163 | @Override 164 | protected synchronized ByteBuffer initialValue() { 165 | ByteBuffer dup = _src.duplicate(); 166 | return dup; 167 | } 168 | } 169 | 170 | @Override 171 | public boolean isClosed() { 172 | return closed; 173 | } 174 | 175 | public String toString() { 176 | return "Mapped page for " + this.pageFile + ", index = " + this.index + "."; 177 | } 178 | 179 | @Override 180 | public String getPageFile() { 181 | return this.pageFile; 182 | } 183 | 184 | @Override 185 | public long getPageIndex() { 186 | return this.index; 187 | } 188 | } -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/utils/Calculator.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.utils; 2 | 3 | public class Calculator { 4 | 5 | 6 | /** 7 | * mod by shift 8 | * 9 | * @param val 10 | * @param bits 11 | * @return 12 | */ 13 | public static long mod(long val, int bits) { 14 | return val - ((val >> bits) << bits); 15 | } 16 | 17 | /** 18 | * multiply by shift 19 | * 20 | * @param val 21 | * @param bits 22 | * @return 23 | */ 24 | public static long mul(long val, int bits) { 25 | return val << bits; 26 | } 27 | 28 | /** 29 | * divide by shift 30 | * 31 | * @param val 32 | * @param bits 33 | * @return 34 | */ 35 | public static long div(long val, int bits) { 36 | return val >> bits; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.utils; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | public class FileUtil { 7 | 8 | /** 9 | * Only check if a given filename is valid according to the OS rules. 10 | * 11 | * You still need to handle other failures when actually creating 12 | * the file (e.g. insufficient permissions, lack of drive space, security restrictions). 13 | * @param file the name of a file 14 | * @return true if the file is valid, false otherwise 15 | */ 16 | public static boolean isFilenameValid(String file) { 17 | File f = new File(file); 18 | try { 19 | f.getCanonicalPath(); 20 | return true; 21 | } catch (IOException e) { 22 | return false; 23 | } 24 | } 25 | 26 | public static void deleteDirectory(File dir) { 27 | if (!dir.exists()) return; 28 | File[] subs = dir.listFiles(); 29 | if (subs != null) { 30 | for (File f : dir.listFiles()) { 31 | if (f.isFile()) { 32 | if(!f.delete()) { 33 | throw new IllegalStateException("delete file failed: "+f); 34 | } 35 | } else { 36 | deleteDirectory(f); 37 | } 38 | } 39 | } 40 | if(!dir.delete()) { 41 | throw new IllegalStateException("delete directory failed: "+dir); 42 | } 43 | } 44 | 45 | public static void deleteFile(File file) { 46 | if (!file.exists() || !file.isFile()) { 47 | return; 48 | } 49 | if (!file.delete()) { 50 | throw new IllegalStateException("delete file failed: "+file); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/com/leansoft/bigqueue/utils/FolderNameValidator.java: -------------------------------------------------------------------------------- 1 | package com.leansoft.bigqueue.utils; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class FolderNameValidator { 6 | 7 | private static final String illegalChars = "/" + '\u0000' + '\u0001' + "-" + '\u001F' + '\u007F' + "-" + '\u009F' + '\uD800' + "-" + '\uF8FF' + '\uFFF0' 8 | + "-" + '\uFFFF'; 9 | private static final Pattern p = Pattern.compile("(^\\.{1,2}$)|[" + illegalChars + "]"); 10 | 11 | public static void validate(String name) { 12 | if (name == null || name.length() == 0) { 13 | throw new IllegalArgumentException("folder name is emtpy"); 14 | } 15 | if(name.length() > 255) { 16 | throw new IllegalArgumentException("folder name is too long"); 17 | } 18 | if (p.matcher(name).find()) { 19 | throw new IllegalArgumentException("folder name [" + name + "] is illegal"); 20 | } 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResAdd.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | import java.io.Serializable; 5 | 6 | public class ResAdd implements Serializable { 7 | public ResultCode status; 8 | public long idx; 9 | 10 | public ResAdd() { 11 | 12 | } 13 | 14 | @ConstructorProperties({ "status" }) 15 | public ResAdd(ResultCode status) { 16 | this.status = status; 17 | this.idx = -1; 18 | } 19 | 20 | @ConstructorProperties({ "status", "idx" }) 21 | public ResAdd(ResultCode status, long idx) { 22 | this.status = status; 23 | this.idx = idx; 24 | } 25 | 26 | public ResultCode getStatus() { 27 | return status; 28 | } 29 | 30 | public long getIdx() { 31 | return idx; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | StringBuilder builder = new StringBuilder(); 37 | builder.append("ResAdd [status=").append(status).append(", idx=").append(idx).append("]"); 38 | return builder.toString(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResData.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | import java.io.Serializable; 5 | 6 | public class ResData implements Serializable { 7 | public ResultCode status; 8 | public String data; 9 | 10 | public ResData() { 11 | 12 | } 13 | 14 | @ConstructorProperties({ "status" }) 15 | public ResData(ResultCode status) { 16 | this.status = status; 17 | this.data = ""; 18 | } 19 | 20 | @ConstructorProperties({ "status", "data" }) 21 | public ResData(ResultCode status, String data) { 22 | this.status = status; 23 | this.data = data; 24 | } 25 | 26 | public ResultCode getStatus() { 27 | return status; 28 | } 29 | 30 | public String getData() { 31 | return data; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | StringBuilder builder = new StringBuilder(); 37 | builder.append("ResData [status=").append(status).append(", data=").append(data).append("]"); 38 | return builder.toString(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResList.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | import java.io.Serializable; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ResList implements Serializable { 9 | public ResultCode status; 10 | public List data; 11 | 12 | public ResList() { 13 | 14 | } 15 | 16 | @ConstructorProperties({ "status" }) 17 | public ResList(ResultCode status) { 18 | this.status = status; 19 | this.data = new ArrayList(); 20 | } 21 | 22 | @ConstructorProperties({ "status", "data" }) 23 | public ResList(ResultCode status, List data) { 24 | this.status = status; 25 | this.data = data; 26 | } 27 | 28 | public ResultCode getStatus() { 29 | return status; 30 | } 31 | 32 | public List getData() { 33 | return data; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | StringBuilder builder = new StringBuilder(); 39 | builder.append("ResList [status=").append(status).append(", data=").append(data).append("]"); 40 | return builder.toString(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResQueueStatus.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | import java.io.Serializable; 5 | 6 | public class ResQueueStatus implements Serializable { 7 | public ResultCode status; 8 | public String queueName; 9 | public long capacity; 10 | public long size; 11 | public long head; 12 | public long tail; 13 | 14 | public ResQueueStatus() { 15 | 16 | } 17 | 18 | @ConstructorProperties({ "status", "queueName" }) 19 | public ResQueueStatus(ResultCode status, String queueName) { 20 | this.status = status; 21 | this.queueName = queueName; 22 | this.capacity = 0; 23 | this.size = 0; 24 | this.head = 0; 25 | this.tail = 0; 26 | } 27 | 28 | @ConstructorProperties({ "status", "queueName", "capacity", "size", "head", "tail" }) 29 | public ResQueueStatus(ResultCode status, String queueName, long capacity, long size, long head, long tail) { 30 | this.status = status; 31 | this.queueName = queueName; 32 | this.capacity = capacity; 33 | this.size = size; 34 | this.head = head; 35 | this.tail = tail; 36 | } 37 | 38 | public ResultCode getStatus() { 39 | return status; 40 | } 41 | 42 | public String getQueueName() { 43 | return queueName; 44 | } 45 | 46 | public long getCapacity() { 47 | return capacity; 48 | } 49 | 50 | public long getSize() { 51 | return size; 52 | } 53 | 54 | public long getHead() { 55 | return head; 56 | } 57 | 58 | public long getTail() { 59 | return tail; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | StringBuilder builder = new StringBuilder(); 65 | builder.append("ResQueueStatus [status=") 66 | .append(status) 67 | .append(", queueName=") 68 | .append(queueName) 69 | .append(", capacity=") 70 | .append(capacity) 71 | .append(", size=") 72 | .append(size) 73 | .append(", head=") 74 | .append(head) 75 | .append(", tail=") 76 | .append(tail) 77 | .append("]"); 78 | return builder.toString(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResSubStatus.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | import java.io.Serializable; 5 | 6 | public class ResSubStatus implements Serializable { 7 | public ResultCode status; 8 | public String queueName; 9 | public long capacity; 10 | public String subName; 11 | public long size; 12 | public long head; 13 | public long tail; 14 | 15 | public ResSubStatus() { 16 | 17 | } 18 | 19 | @ConstructorProperties({ "status", "queueName", "subName" }) 20 | public ResSubStatus(ResultCode status, String queueName, String subName) { 21 | this.status = status; 22 | this.queueName = queueName; 23 | this.subName = subName; 24 | this.capacity = 0; 25 | this.size = 0; 26 | this.head = 0; 27 | this.tail = 0; 28 | } 29 | 30 | @ConstructorProperties({ "status", "queueName", "capacity", "subName", "size", "head", "tail" }) 31 | public ResSubStatus(ResultCode status, String queueName, long capacity, String subName, long size, long head, long tail) { 32 | this.status = status; 33 | this.queueName = queueName; 34 | this.capacity = capacity; 35 | this.subName = subName; 36 | this.size = size; 37 | this.head = head; 38 | this.tail = tail; 39 | } 40 | 41 | public ResultCode getStatus() { 42 | return status; 43 | } 44 | 45 | public String getQueueName() { 46 | return queueName; 47 | } 48 | 49 | public long getCapacity() { 50 | return capacity; 51 | } 52 | 53 | public String getSubName() { 54 | return subName; 55 | } 56 | 57 | public long getSize() { 58 | return size; 59 | } 60 | 61 | public long getHead() { 62 | return head; 63 | } 64 | 65 | public long getTail() { 66 | return tail; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | StringBuilder builder = new StringBuilder(); 72 | builder.append("ResSubStatus [status=") 73 | .append(status) 74 | .append(", queueName=") 75 | .append(queueName) 76 | .append(", capacity=") 77 | .append(capacity) 78 | .append(", subName=") 79 | .append(subName) 80 | .append(", size=") 81 | .append(size) 82 | .append(", head=") 83 | .append(head) 84 | .append(", tail=") 85 | .append(tail) 86 | .append("]"); 87 | return builder.toString(); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/wjw/psqueue/msg/ResultCode.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.msg; 2 | 3 | import java.beans.ConstructorProperties; 4 | 5 | public class ResultCode { 6 | public final static ResultCode SUCCESS = new ResultCode(0, "ok"); 7 | public final static ResultCode CMD_INVALID = new ResultCode(1, "Invalid opt command!"); 8 | public final static ResultCode INTERNAL_ERROR = new ResultCode(2, "internal error"); 9 | public final static ResultCode AUTHENTICATION_FAILURE = new ResultCode(3, "authentication failure"); 10 | public final static ResultCode QUEUE_NOT_EXIST = new ResultCode(4, "queue not exist"); 11 | public final static ResultCode SUB_NOT_EXIST = new ResultCode(5, "Subscriber not exist"); 12 | public final static ResultCode QUEUE_NAME_INVALID = new ResultCode(6, "invalid queue name!queue name not include:? \\ / : | < > * and _META_"); 13 | public final static ResultCode SUB_NAME_INVALID = new ResultCode(7, "invalid Subscriber name!Subscriber name not include:? \\ / : | < > * and _META_"); 14 | public final static ResultCode SUB_IS_EXIST = new ResultCode(8, "Subscriber is already exist"); 15 | public final static ResultCode QUEUE_IS_EXIST = new ResultCode(9, "queue is already exist"); 16 | public final static ResultCode QUEUE_IS_EMPTY = new ResultCode(10, "queue is empty"); 17 | public final static ResultCode QUEUE_CREATE_ERROR = new ResultCode(11, "queue create error"); 18 | public final static ResultCode QUEUE_REMOVE_ERROR = new ResultCode(12, "queue remove error"); 19 | public final static ResultCode SUB_REMOVE_ERROR = new ResultCode(13, "Subscriber remove error"); 20 | public final static ResultCode INDEX_OUT_OF_BOUNDS = new ResultCode(14, "index out of bounds"); 21 | public final static ResultCode QUEUE_ADD_ERROR = new ResultCode(15, "queue add error"); 22 | public final static ResultCode QUEUE_POLL_ERROR = new ResultCode(16, "queue poll error"); 23 | public final static ResultCode ALL_MESSAGE_CONSUMED = new ResultCode(17, "all message consumed"); 24 | public final static ResultCode QUEUE_CAPACITY_INVALID = new ResultCode(18, "queue capacity must >=1000000L AND <=1000000000L"); 25 | public final static ResultCode SUB_TAILPOS_ERROR = new ResultCode(19, "Subscriber set tail pos error"); 26 | 27 | public int code; 28 | public String msg; 29 | 30 | public ResultCode() { 31 | } 32 | 33 | @ConstructorProperties({ "code", "msg" }) 34 | public ResultCode(final int code, final String msg) { 35 | this.code = code; 36 | this.msg = msg; 37 | } 38 | 39 | public int getCode() { 40 | return code; 41 | } 42 | 43 | public String getMsg() { 44 | return msg; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | StringBuilder builder = new StringBuilder(); 50 | builder.append("ResultCode [code=").append(code).append(", msg=").append(msg).append("]"); 51 | return builder.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/App.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/src/wjw/psqueue/server/App.java -------------------------------------------------------------------------------- /src/wjw/psqueue/server/Conf.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/src/wjw/psqueue/server/Conf.java -------------------------------------------------------------------------------- /src/wjw/psqueue/server/HttpRequestHandler.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/src/wjw/psqueue/server/HttpRequestHandler.java -------------------------------------------------------------------------------- /src/wjw/psqueue/server/HttpServerChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.http.HttpObjectAggregator; 7 | import io.netty.handler.codec.http.HttpRequestDecoder; 8 | import io.netty.handler.codec.http.HttpResponseEncoder; 9 | 10 | public class HttpServerChannelInitializer extends ChannelInitializer { 11 | private App _app; 12 | 13 | public HttpServerChannelInitializer(App app) { 14 | _app = app; 15 | } 16 | 17 | @Override 18 | public void initChannel(SocketChannel ch) throws Exception { 19 | // Create a default pipeline implementation. 20 | ChannelPipeline pipeline = ch.pipeline(); 21 | 22 | // Uncomment the following line if you want HTTPS 23 | //SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); 24 | //engine.setUseClientMode(false); 25 | //p.addLast("ssl", new SslHandler(engine)); 26 | 27 | pipeline.addLast("decoder", new HttpRequestDecoder()); 28 | pipeline.addLast("aggregator", new HttpObjectAggregator(1048576)); 29 | pipeline.addLast("encoder", new HttpResponseEncoder()); 30 | 31 | // Remove the following line if you don't want automatic content compression. 32 | //pipeline.addLast("deflater", new HttpContentCompressor()); 33 | 34 | pipeline.addLast("handler", new HttpRequestHandler(_app)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/Wrapper.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server; 2 | 3 | import org.tanukisoftware.wrapper.WrapperManager; 4 | import org.tanukisoftware.wrapper.WrapperSimpleApp; 5 | 6 | public class Wrapper extends WrapperSimpleApp { 7 | @Override 8 | public Integer start(String[] args) { 9 | Integer result = super.start(args); 10 | 11 | WrapperManager.log(WrapperManager.WRAPPER_LOG_LEVEL_FATAL, "Started Wrapper PSQueue!"); 12 | 13 | return result; 14 | } 15 | 16 | @Override 17 | public int stop(int exitCode) { 18 | int result = super.stop(exitCode); 19 | 20 | WrapperManager.log(WrapperManager.WRAPPER_LOG_LEVEL_FATAL, "Stoped Wrapper PSQueue!"); 21 | 22 | return result; 23 | } 24 | 25 | protected Wrapper(String[] strings) { 26 | super(strings); 27 | } 28 | 29 | public static void main(String args[]) { 30 | new Wrapper(args); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/jmx/AppMXBean.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjw465150/PSQueueServer/96c568808440c9a609309ac13d58eef4ae16d3f7/src/wjw/psqueue/server/jmx/AppMXBean.java -------------------------------------------------------------------------------- /src/wjw/psqueue/server/jmx/JMXTools.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server.jmx; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Method; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class JMXTools { 9 | private static org.slf4j.Logger _log = org.slf4j.LoggerFactory.getLogger(JMXTools.class); 10 | 11 | public static final Map> mapJMXPrimitive = new HashMap>(); 12 | static { 13 | mapJMXPrimitive.put("byte", byte.class); 14 | mapJMXPrimitive.put("short", short.class); 15 | mapJMXPrimitive.put("int", int.class); 16 | mapJMXPrimitive.put("long", long.class); 17 | mapJMXPrimitive.put("float", float.class); 18 | mapJMXPrimitive.put("double", double.class); 19 | mapJMXPrimitive.put("char", char.class); 20 | mapJMXPrimitive.put("boolean", boolean.class); 21 | } 22 | 23 | private JMXTools() { 24 | } 25 | 26 | /** 27 | * Java 1.5 and above supports the ability to register the WrapperManager 28 | * MBean internally. 29 | */ 30 | @SuppressWarnings("rawtypes") 31 | static public void registerMBean(Object mbean, String name) { 32 | Class classManagementFactory; 33 | Class classMBeanServer; 34 | Class classObjectName; 35 | try { 36 | classManagementFactory = Class.forName("java.lang.management.ManagementFactory"); 37 | classMBeanServer = Class.forName("javax.management.MBeanServer"); 38 | classObjectName = Class.forName("javax.management.ObjectName"); 39 | } catch (ClassNotFoundException e) { 40 | _log.error("Registering MBeans not supported by current JVM:" + name); 41 | return; 42 | } 43 | 44 | try { 45 | // This code uses reflection so it combiles on older JVMs. 46 | // The original code is as follows: 47 | // javax.management.MBeanServer mbs = 48 | // java.lang.management.ManagementFactory.getPlatformMBeanServer(); 49 | // javax.management.ObjectName oName = new javax.management.ObjectName( name ); 50 | // mbs.registerMBean( mbean, oName ); 51 | 52 | // The version of the above code using reflection follows. 53 | Method methodGetPlatformMBeanServer = classManagementFactory.getMethod("getPlatformMBeanServer", (Class[]) null); 54 | Constructor constructorObjectName = classObjectName.getConstructor(new Class[] { String.class }); 55 | Method methodRegisterMBean = classMBeanServer.getMethod("registerMBean", new Class[] { Object.class, classObjectName }); 56 | Object mbs = methodGetPlatformMBeanServer.invoke(null, (Object[]) null); 57 | Object oName = constructorObjectName.newInstance(new Object[] { name }); 58 | methodRegisterMBean.invoke(mbs, new Object[] { mbean, oName }); 59 | 60 | _log.info("Registered MBean with Platform MBean Server:" + name); 61 | } catch (Throwable t) { 62 | if (t instanceof ClassNotFoundException) { 63 | _log.error("Using MBean requires at least a JVM version 1.5."); 64 | } 65 | _log.error("Unable to register the " + name + " MBean."); 66 | t.printStackTrace(); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/jmx/annotation/Description.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server.jmx.annotation; 2 | 3 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 4 | import static java.lang.annotation.ElementType.FIELD; 5 | import static java.lang.annotation.ElementType.METHOD; 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | import java.lang.annotation.Documented; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.Target; 13 | 14 | @Documented 15 | @Retention(value = RUNTIME) 16 | @Target(value = { CONSTRUCTOR, FIELD, METHOD, PARAMETER, TYPE }) 17 | public @interface Description { 18 | String name() default ""; 19 | 20 | String description() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/jmx/annotation/MBean.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server.jmx.annotation; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | @Documented 12 | @Retention(value = RUNTIME) 13 | @Target(value = TYPE) 14 | @Inherited 15 | public @interface MBean { 16 | String objectName() default ""; 17 | 18 | String description() default ""; 19 | } 20 | -------------------------------------------------------------------------------- /src/wjw/psqueue/server/jmx/annotation/ManagedOperation.java: -------------------------------------------------------------------------------- 1 | package wjw.psqueue.server.jmx.annotation; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | @Documented 11 | @Retention(value = RUNTIME) 12 | @Target(value = { METHOD }) 13 | public @interface ManagedOperation { 14 | String description() default ""; 15 | } 16 | --------------------------------------------------------------------------------