2014-02-15 1 views
2

내가하고 싶은 일은 아주 간단해야합니다. 아래의 솔루션에 도달했습니다. 그것을하는 방법입니다 또는 코드에서 아무것도 리팩터링해야합니다.병렬 프로세스를 생성하고 모든 프로세스가 완료 될 때까지 기다렸다가 다시 실행하십시오.

아래 코드 ... 몇 가지 병렬 프로세스를 만들고이를 다시하고 다시하고 다시 코드를 다시 실행 실행 완료 될 때까지 기다려야한다

스크립트가 한 번 십분에서 cron 작업에 의해 트리거됩니다, 스크립트가 실행 중이면 아무 작업도하지 말고 그렇지 않으면 작업 프로세스를 시작하십시오.

bash 프로그래밍에 익숙하지 않으므로 어떤 통찰력이라도 높이 평가됩니다.

#!/bin/bash 

# paths 
THISPATH="$(cd "$(dirname "$0")" && pwd)" 

# make sure we move in the working directory 
cd $THISPATH 

# console init path 
CONSOLEPATH="$(cd ../../ && pwd)/console.php" 

# command line arguments 
daemon=0 
PHPPATH="/usr/bin/php" 
help=0 

# flag for binary search 
LOOKEDFORPHP=0 

# arguments init 
while getopts d:p:h: opt; do 
    case $opt in 
    d) 
     daemon=$OPTARG 
     ;; 
    p) 
     PHPPATH=$OPTARG 
     LOOKEDFORPHP=1 
     ;; 
    h) 
     help=$OPTARG 
     ;; 
    esac 
done 

shift $((OPTIND - 1)) 


# allow only one process 
processesLength=$(ps aux | grep -v "grep" | grep -c $THISPATH/send-campaigns-daemon.sh) 
if [ ${processesLength:-0} -gt 2 ]; then 
    # The process is already running 
    exit 0 
fi 

if [ $help -eq 1 ]; then 
    echo "---------------------------------------------------------------" 
    echo "| Usage: send-campaigns-daemon.sh        |" 
    echo "| To force PHP CLI binary :         |" 
    echo "| send-campaigns-daemon.sh -p /path/to/php-cli/binary   |" 
    echo "---------------------------------------------------------------" 
    exit 0 
fi 

# php executable path, find it if not provided 
if [ $PHPPATH ] && [ ! -f $PHPPATH ] && [ $LOOKEDFORPHP -eq 0 ]; then 
    phpVariants=("php-cli" "php5-cli" "php5" "php") 
    LOOKEDFORPHP=1 

    for i in "${phpVariants[@]}" 
    do 
     which $i >/dev/null 2>&1 
     if [ $? -eq 0 ]; then 
      PHPPATH=$(which $i) 
     fi 
    done 
fi 

if [ ! $PHPPATH ] || [ ! -f $PHPPATH ]; then 
    # Did not find PHP 
    exit 1 
fi 


# load options from app 
parallelProcessesPerCampaign=3 
campaignsAtOnce=10 
subscribersAtOnce=300 
sleepTime=30 

function loadOptions { 
    local COMMAND="$PHPPATH $CONSOLEPATH option get_option --name=%s --default=%d" 
    parallelProcessesPerCampaign=$(printf "$COMMAND" "system.cron.send_campaigns.parallel_processes_per_campaign" 3) 
    campaignsAtOnce=$(printf "$COMMAND" "system.cron.send_campaigns.campaigns_at_once" 10) 
    subscribersAtOnce=$(printf "$COMMAND" "system.cron.send_campaigns.subscribers_at_once" 300) 
    sleepTime=$(printf "$COMMAND" "system.cron.send_campaigns.pause" 30) 

    parallelProcessesPerCampaign=$($parallelProcessesPerCampaign) 
    campaignsAtOnce=$($campaignsAtOnce) 
    subscribersAtOnce=$($subscribersAtOnce) 
    sleepTime=$($sleepTime) 
} 

# define the daemon function that will stay in loop 
function daemon { 
    loadOptions 
    local pids=() 
    local k=0 
    local i=0 
    local COMMAND="$PHPPATH -q $CONSOLEPATH send-campaigns --campaigns_offset=%d --campaigns_limit=%d --subscribers_offset=%d --subscribers_limit=%d --parallel_process_number=%d --parallel_processes_count=%d --usleep=%d --from_daemon=1" 

    while [ $i -lt $campaignsAtOnce ] 
    do 
     while [ $k -lt $parallelProcessesPerCampaign ] 
     do 
      parallelProcessNumber=$(($k + 1)) 
      usleep=$(($k * 10 + $i * 10)) 
      CMD=$(printf "$COMMAND" $i 1 $(($subscribersAtOnce * $k)) $subscribersAtOnce $parallelProcessNumber $parallelProcessesPerCampaign $usleep) 
      $CMD > /dev/null 2>&1 & 
      pids+=($!) 
      k=$((k + 1)) 
     done 
     i=$((i + 1)) 
    done 

    waitForPids pids 

    sleep $sleepTime 

    daemon 
} 

function daemonize { 
    $THISPATH/send-campaigns-daemon.sh -d 1 -p $PHPPATH > /dev/null 2>&1 & 
} 

function waitForPids { 
    stillRunning=0 
    for i in "${pids[@]}" 
    do 
     if ps -p $i > /dev/null 
     then 
      stillRunning=1 
      break 
     fi 
    done 

    if [ $stillRunning -eq 1 ]; then 
     sleep 0.5 
     waitForPids pids 
    fi 

    return 0 
} 

if [ $daemon -eq 1 ]; then 
    daemon 
else 
    daemonize 
fi 

exit 0 
+0

완전한 해결책을 코딩하려는 기분이 아니므로 몇 가지 코멘트를 드리겠습니다. 'THISPATH' 대신'realpath'를 사용하여 스크립트 자체의 실제 경로를 찾을 수 있는지 확인하십시오. 자신이 시작한 프로세스가 아직 실행 중인지 확인하려면 해당 프로세스에서 열려있는 파일에 대해'fuser'를 시도하십시오. 이는 종종 ps grepping보다 훨씬 쉽습니다. bash가 tail-recursion을 알고 있다고 생각하지 않으므로 waitForPids가 스택 오버플로처럼 보입니다. 그리고 재귀 호출은 리터럴 문자열 pids를 함수로 사용되지 않는 매개 변수로 사용합니다. – Harald

+0

@Harald는 문제를 지적 해 주셔서 감사합니다. 많은 도움이되었습니다. realpath는 내가 아는 한 모든 곳에서 사용할 수 없습니다. 지금까지 프로세스의 단일 인스턴스를 유지하면서 mkdir로 스크립트를 잠그고 트랩을 사용하여 잠금을 해제하기로 결정했습니다. 지금 당장 트릭을하는 것 같다. – Twisted1919

+0

''bash''의'wait' 내장을 고려 했습니까? 네가 원하는 걸 할 수 없어? – janos

답변

1

좋아, 그래서 내가 내 자신의 질문에 대답 할 수있는 것 같아요 많은 시험을 거친 후 올바른 대답.
그래서 여기에 의견/에코하지 않고, 간단하게, 최종 버전입니다 :

#!/bin/bash 

sleep 2 

DIR="$(cd "$(dirname "$0")" && pwd)" 
FILE_NAME="$(basename "$0")" 
COMMAND_FILE_PATH="$DIR/$FILE_NAME" 

if [ ! -f "$COMMAND_FILE_PATH" ]; then 
    exit 1 
fi 

cd $DIR 

CONSOLE_PATH="$(cd ../../ && pwd)/console.php" 
PHP_PATH="/usr/bin/php" 
help=0 
LOOKED_FOR_PHP=0 

while getopts p:h: opt; do 
    case $opt in 
    p) 
     PHP_PATH=$OPTARG 
     LOOKED_FOR_PHP=1 
     ;; 
    h) 
     help=$OPTARG 
     ;; 
    esac 
done 
shift $((OPTIND - 1)) 

if [ $help -eq 1 ]; then 
    printf "%s\n" "HELP INFO" 
    exit 0 
fi 

if [ "$PHP_PATH" ] && [ ! -f "$PHP_PATH" ] && [ "$LOOKED_FOR_PHP" -eq 0 ]; then 
    php_variants=("php-cli" "php5-cli" "php5" "php") 
    LOOKED_FOR_PHP=1 
    for i in "${php_variants[@]}" 
    do 
     which $i >/dev/null 2>&1 
     if [ $? -eq 0 ]; then 
      PHP_PATH="$(which $i)" 
      break 
     fi 
    done 
fi 

if [ ! "$PHP_PATH" ] || [ ! -f "$PHP_PATH" ]; then 
    exit 1 
fi 

LOCK_BASE_PATH="$(cd ../../../common/runtime && pwd)/shell-pids" 
LOCK_PATH="$LOCK_BASE_PATH/send-campaigns-daemon.pid" 

function remove_lock { 
    if [ -d "$LOCK_PATH" ]; then 
     rmdir "$LOCK_PATH" > /dev/null 2>&1 
    fi 
    exit 0 
} 

if [ ! -d "$LOCK_BASE_PATH" ]; then 
    if ! mkdir -p "$LOCK_BASE_PATH" > /dev/null 2>&1; then 
     exit 1 
    fi 
fi 

process_running=0 
if mkdir "$LOCK_PATH" > /dev/null 2>&1; then 
    process_running=0 
else 
    process_running=1 
fi 

if [ $process_running -eq 1 ]; then 
    exit 0 
fi 

trap "remove_lock" 1 2 3 15 

COMMAND="$PHP_PATH $CONSOLE_PATH option get_option --name=%s --default=%d" 
parallel_processes_per_campaign=$(printf "$COMMAND" "system.cron.send_campaigns.parallel_processes_per_campaign" 3) 
campaigns_at_once=$(printf "$COMMAND" "system.cron.send_campaigns.campaigns_at_once" 10) 
subscribers_at_once=$(printf "$COMMAND" "system.cron.send_campaigns.subscribers_at_once" 300) 
sleep_time=$(printf "$COMMAND" "system.cron.send_campaigns.pause" 30) 

parallel_processes_per_campaign=$($parallel_processes_per_campaign) 
campaigns_at_once=$($campaigns_at_once) 
subscribers_at_once=$($subscribers_at_once) 
sleep_time=$($sleep_time) 

k=0 
i=0 
pp=0 
COMMAND="$PHP_PATH -q $CONSOLE_PATH send-campaigns --campaigns_offset=%d --campaigns_limit=%d --subscribers_offset=%d --subscribers_limit=%d --parallel_process_number=%d --parallel_processes_count=%d --usleep=%d --from_daemon=1" 

while [ $i -lt $campaigns_at_once ] 
do 
    while [ $k -lt $parallel_processes_per_campaign ] 
    do 
     parallel_process_number=$(($k + 1)) 
     usleep=$(($k * 10 + $i * 10)) 
     CMD=$(printf "$COMMAND" $i 1 $(($subscribers_at_once * $k)) $subscribers_at_once $parallel_process_number $parallel_processes_per_campaign $usleep) 
     $CMD > /dev/null 2>&1 & 
     k=$((k + 1)) 
     pp=$((pp + 1)) 
    done 
    i=$((i + 1)) 
done 

wait 

sleep ${sleep_time:-30} 

$COMMAND_FILE_PATH -p "$PHP_PATH" > /dev/null 2>&1 & 
remove_lock 
exit 0 
+0

'mkdir -p $ lock'은 잠금이 이미 존재하면 성공을 반환합니다. – ams

1

스크립트를 시작할 때이 스크립트가 실행 중인지 확인하기 위해 잠금 파일을 만듭니다. 스크립트가 완료되면 잠금 파일을 삭제하십시오. 누군가가 프로세스가 실행되는 동안 프로세스를 종료하면 잠금 파일은 얼마나 오래되었는지 테스트하고 정의 된 값보다 오래된 경우 삭제해도 영구히 남아 있습니다. 예를 들어,

#!/bin/bash 

# 10 min 
LOCK_MAX=600 

typedef LOCKFILE=/var/lock/${0##*/}.lock 

if [[ -f $LOCKFILE ]] ; then 
    TIMEINI=$(stat -c %X $LOCKFILE) 
    SEGS=$(($(date +%s) - $TIEMPOINI)) 
    if [[ $SEGS -gt $LOCK_MAX ]] ; then 
     reportLocking or somethig to inform you 
     # Kill old intance ??? 
     OLDPID=$(<$LOCKFILE) 
     [[ -e /proc/$OLDPID ]] && kill -9 $OLDPID 
     # Next time that the program is run, there is no lock file and it will run. 
     rm $LOCKFILE 
    fi 
    exit 65 
fi 
# Save PID of this instance to the lock file 
echo "$$" > $LOCKFILE 

### Your code go here 

# Remove the lock file before script finish 
[[ -e $LOCKFILE ]] && rm $LOCKFILE 
exit 0 
+0

프로세스 존재에 대한 메모리 검사로'ps -p'를 실행하는 것보다 파일 존재를 확인하는 것이 더 효과적이라고 설명 할 수 있습니까? – Twisted1919

+0

테스트 및 설정은 잠금 파일을 만드는 잘못된 방법입니다. – ams

1

here에서 : here

#!/bin/bash 

... 
echo PARALLEL_JOBS:${PARALLEL_JOBS:=1} 

declare -a tests=($(.../find_what_to_run)) 
echo "${tests[@]}" | \ 
    xargs -d' ' -n1 -P${PARALLEL_JOBS} -I {} bash -c ".../run_that {}" || { echo "FAILURE"; exit 1; } 

echo "SUCCESS" 

하고 있습니다 닉 휴대용 잠금에 대한 코드 fuser

+0

답변에 감사하지만 예제에서 너무 많이 얻지는 않았습니다.이 시점에서 나에게 너무 비싸다고 생각합니다. 출구에서 mkdir 및 trap으로 잠그는 간단한 해결책을 찾았습니다. – Twisted1919

1

일반적으로는 잠금 파일이 아닌 잠금 경로입니다. 프로세스 모니터링을 위해 잠금 파일에 PID를 보유합니다. 이 경우 잠금 디렉토리는 PID 정보를 보유하지 않습니다. 또한 스크립트는 잠금을 지우지 않고 프로세스를 부적절하게 종료 한 경우 시작될 때 PID 파일/디렉토리 유지 관리를 수행하지 않습니다.

나는이 점을 염두에두고 첫 번째 스크립트를 더 좋아합니다. PID의 실행을 직접 모니터링하는 것이 더 깨끗합니다. 유일한 문제는 cron을 사용하여 두 번째 인스턴스를 시작한 경우 첫 번째 인스턴스에 연결된 PID를 인식하지 못하는 것입니다.

processLength -gt 2는 2이며 1 프로세스가 실행 중이기 때문에 프로세스 스레드를 복제하지 않습니다.

daemonize는 그다지 유용하지 않은 데몬으로 스크립트를 리콜하는 것 같습니다. 또한 함수와 이름이 같은 변수는 효과적이지 않습니다.

+0

linux에서 디렉토리를 생성하는 것은 원자 적이며 경쟁 조건 (AFAIK)이 아니므로 잠금은 디렉토리입니다. 필자는 pid 디렉토리에 pid 정보가 필요없고 디렉토리 만 있으면 프로세스가 실행 중임을 의미합니다. 프로세스가 끝나면 디렉토리가 제거되어 새 프로세스를 시작할 수 있습니다. 또한 부적 절한 종료시 pid를 지우는 함정 기능이 있습니다. 내가 여기서 뭔가를 놓치고 있니? (bash 광대 한 지식의 옆에 물론) – Twisted1919

1

로크 파일을하는 올바른 방법은 다음과 같이이다 :

# Create a temporary file 
echo $$ > ${LOCKFILE}.tmp$$ 

# Try the lock; ln without -f is atomic 
if ln ${LOCKFILE}.tmp$$ ${LOCKFILE}; then 
    # we got the lock 
else 
    # we didn't get the lock 
fi 

# Tidy up the temporary file 
rm ${LOCKFILE}.tmp$$ 

그리고 잠금 해제 :

# Unlock 
rm ${LOCKFILE} 

중요한 것은이를 사용하여 한쪽으로 잠금 파일을 생성하는 것입니다 고유 한 이름을 입력 한 다음 실제 이름과 연결하십시오. 이것은 원자 적 연산이므로 안전해야합니다.

"테스트 및 설정"을 수행하는 모든 솔루션은 처리 할 경쟁 조건을 제공합니다. 예, 정렬 할 수 있지만 추가 코드를 작성하게됩니다.

관련 문제