동일한 파이프 라인에서 동일한 파일을 읽고 쓰는 방법은 항상“실패”합니까?


9

다음 스크립트가 있다고 가정 해보십시오.

#!/bin/bash
for i in $(seq 1000)
do
    cp /etc/passwd tmp
    cat tmp | head -1 | head -1 | head -1 > tmp  #this is the key line
    cat tmp
done

키 라인에서 tmp때로는 실패 하는 동일한 파일 을 읽고 씁니다 .

(파이프 라인의 프로세스가 병렬로 실행되기 때문에 경쟁 조건 때문이라고 읽었습니다. 이유는 이해할 수 없습니다. 각각 head이전 데이터에서 데이터를 가져와야합니까? 그렇지 않습니까? 하지만 당신도 대답 할 수 있습니다.)

스크립트를 실행하면 약 200 줄이 출력됩니다. 이 스크립트가 항상 0 줄을 출력하도록 할 수있는 방법이 있습니까 (따라서 I / O 리디렉션 tmp이 항상 먼저 준비되므로 데이터가 항상 파괴됩니다)? 명확히하기 위해이 스크립트가 아닌 시스템 설정을 변경하는 것을 의미합니다.

당신의 아이디어에 감사드립니다.

답변:


2

Gilles의 답변은 경쟁 조건을 설명합니다. 나는이 부분에 대답 할 것입니다.

이 스크립트가 항상 0 줄을 출력하도록 할 수있는 방법이 있습니까 (따라서 tmp 로의 I / O 리디렉션이 항상 먼저 준비되므로 데이터가 항상 파괴됩니다)? 분명히, 나는 시스템 설정을 변경하는 것을 의미합니다

이 도구가 이미 존재하지만 IDK를 구현하는 방법에 대한 아이디어가 있다면 IDK입니다. (하지만이되지 않을 것주의 항상 0 라인, 단지 유용한 테스터 그 어획량 쉽게 같은 간단한 인종, 그리고 약간 더 복잡 인종. 참조 @Gilles '코멘트 .) 스크립트가 안전하다는 것을 그것은 것하지 보장 하지만, 힘 ARM과 같이 약하게 정렬되지 않은 x86 CPU를 포함하여 다른 CPU에서 멀티 스레드 프로그램을 테스트하는 것과 유사한 테스트에 유용한 도구입니다.

당신은 그것을 그것을 실행할 것입니다 racechecker bash foo.sh

시설 / 추적을 차단 같은 시스템 호출을 사용 strace -f하고 ltrace -f모든 자식 프로세스에 연결하기 위해 사용합니다. (Linux에서는 ptraceGDB 및 기타 디버거 에서 중단 점을 설정하고 단일 단계를 수행하며 다른 프로세스의 메모리 / 레지스터를 수정하기 위해 사용 하는 것과 동일한 시스템 호출 입니다.)

계측기 openopenat시스템 호출 :이 도구에서 실행중인 프로세스 open(2)시스템 호출 (또는 openat)을 사용 O_RDONLY하면 1/2 또는 1 초 동안 휴면 상태가됩니다. 다른 open시스템 호출 (특히 포함 O_TRUNC)이 지연없이 실행되도록합니다.

이렇게하면 시스템로드가 높지 않거나 다른 읽기 이후까지 잘림이 발생하지 않은 복잡한 경쟁 조건 인 경우 거의 모든 경쟁 조건에서 라이터가 경쟁에서 이길 수 있습니다. 따라서 임의의 open()s (및 아마도 read()s 또는 writes)가 지연 되면이 도구의 탐지 능력이 향상되지만 지연 시뮬레이터를 사용하여 무한한 시간 동안 테스트하지 않고도 발생할 수있는 모든 상황을 처리 할 수 ​​있습니다. 실제 세계에서는 스크립트를주의 깊게 읽고 그렇지 않다는 것을 증명하지 않으면 스크립트에 인종이 없는지 확신 할 수 없습니다.


당신은 아마 화이트리스트 (안 지연에 필요 open에있는 파일에 대한) /usr/bin/usr/lib영원히하지 않는 프로세스 시작 정도. ( 부모 쉘 자체가 잘림을 수행하더라도 런타임 동적 링크는 open()여러 파일에 대한 조회 strace -eopen /bin/true또는 /bin/ls언젠가는 필요하지만 괜찮습니다. 그러나이 도구가 스크립트를 부당하게 느리게 만들지 않는 것이 좋습니다.)

또는 호출 프로세스가 처음부터자를 권한이없는 모든 파일을 허용 목록에 추가 할 수 있습니다. 즉, 추적 프로세스는 access(2)실제로 open()파일에 원하는 프로세스를 일시 중단하기 전에 시스템 호출을 수행 할 수 있습니다 .


racechecker자체는 쉘이 아닌 C로 작성해야하지만 strace시작 코드로 코드를 사용할 수 있으며 구현하는 데 많은 작업이 필요하지 않을 수 있습니다.

FUSE 파일 시스템과 동일한 기능 을 사용할 수 있습니다. 순수 패스 스루 파일 시스템의 FUSE 예제가있을 수 있으므로 open()함수에 검사를 추가 하여 읽기 전용 열기를 위해 휴면 상태로 만들지 만 바로 자르기를 할 수 있습니다.


레이스 체커에 대한 당신의 아이디어는 실제로 작동하지 않습니다. 첫째, 타임 아웃이 신뢰할 수 없다는 문제가 있습니다. 언젠가 다른 사람이 예상보다 오래 걸릴 것입니다 (빌드 또는 테스트 스크립트의 고전적인 문제입니다. 잠시 동안 작동하고 디버그하기 어려운 방식으로 실패합니다) 워크로드가 확장되고 많은 것들이 병렬로 실행될 때). 그러나 이것을 넘어서서, 당신은 어느 개방을 지연시킬 것입니까? 흥미로운 것을 감지하려면 다른 지연 패턴으로 많은 런을 수행하고 결과를 비교해야합니다.
Gilles 'SO- 악마 그만해

@Gilles : 그렇습니다. 합리적으로 짧은 지연으로 인해 잘림이 레이스에서 이길 것이라고 보장 하지는 않습니다 (지시 한대로로드가 많은 머신에서). 여기서 아이디어는 항상 사용하는 것이 아니라 스크립트를 몇 번 테스트racechecker 하는 데 사용한다는 것 입니다. 그리고 10 초와 같이 더 높게 설정하려는 매우로드 된 머신의 사용자를 위해 읽기 가능한 절전 시간을 구성 할 수 있습니다. 또는 파일을 많이 다시 여는 길거나 비효율적 인 스크립트의 경우 0.1 초처럼 낮게 설정하십시오 .
Peter Cordes

@Gilles : 다양한 지연 패턴에 대한 좋은 아이디어는 OP의 경우처럼 "한 번 쉘이 작동하는 방식을 알 수있는"파이프 라인과 같은 단순한 파이프 라인보다 더 많은 경쟁을 포착 할 수 있습니다. 그러나 "어떻게 열리나요?" 화이트리스트 또는 프로세스 시작을 지연시키지 않는 다른 방법으로 읽기 전용 열기
Peter Cordes

다른 프로세스가 완료 될 때까지 잘리지 않는 백그라운드 작업을 가진 더 복잡한 경쟁에 대해 생각하고 있습니까? 예, 그것을 잡기 위해 무작위 변형이 필요할 수 있습니다. 또는 프로세스 트리를보고 일반적인 순서를 바꾸려고 "초기"읽기를 지연시킵니다. 점점 더 많은 재정렬 가능성을 시뮬레이션하기 위해 도구를 더욱 복잡하게 만들 수 있지만, 멀티 태스킹을 수행하는 경우 여전히 프로그램을 올바르게 설계해야합니다. 자동화 된 테스트는 가능한 문제가 더 제한적인 간단한 스크립트에 유용 할 수 있습니다.
Peter Cordes

멀티 스레드 코드, 특히 잠금없는 알고리즘을 테스트하는 것과 매우 유사합니다. 정확한 이유에 대한 논리적 추론은 테스트뿐만 아니라 테스트가 매우 중요합니다. 허점을 모두 닫지 않은 경우 문제가됩니다. 그러나 ARM 또는 PowerPC와 같이 약하게 정렬 된 아키텍처에서 테스트하는 것은 실제로 좋은 아이디어입니다. 시스템에서 인위적으로 지연되는 스크립트를 테스트하면 일부 경쟁이 발생할 수 있으므로 아무것도 아닌 것보다 낫습니다. 잡을 수없는 버그를 항상 소개 할 수 있습니다!
Peter Cordes

18

경쟁 조건이있는 이유

파이프의 양면은 차례로 병렬로 실행됩니다. 이것을 보여주는 매우 간단한 방법이 있습니다 : run

time sleep 1 | sleep 1

2 초가 아닌 1 초가 걸립니다.

쉘은 두 개의 하위 프로세스를 시작하고 두 프로세스가 완료되기를 기다립니다. 이 두 과정은 병렬로 실행 :이 때 그들 중 하나가 다른와 동기화 할 유일한 이유는 필요가 다른 기다릴. 가장 일반적인 동기화 지점은 오른쪽이 표준 입력에서 데이터 읽기를 기다리는 것을 차단하고 왼쪽이 더 많은 데이터를 쓸 때 차단 해제됩니다. 오른쪽이 더 많은 데이터를 읽을 때까지 (오른쪽에서 더 많은 데이터를 읽을 때까지 오른쪽에서 데이터 읽기 속도가 느리고 왼쪽 블록이 쓰기 작업을 수행하는 경우에도 반대가 발생할 수 있습니다. 커널이지만 최대 크기는 작습니다).

동기화 지점을 관찰하려면 다음 명령을 관찰하십시오 ( sh -x각 명령이 실행될 때 인쇄).

time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'

관찰 한 내용이 편할 때까지 변형을 가지고 연주하십시오.

복합 명령이 주어지면

cat tmp | head -1 > tmp

왼쪽 프로세스는 다음을 수행합니다 (내 설명과 관련된 단계 만 나열했습니다).

  1. cat인수를 사용 하여 외부 프로그램 을 실행하십시오 tmp.
  2. tmp읽기 위해 엽니 다 .
  3. 파일 끝에 도달하지 않은 상태에서 파일에서 청크를 읽고 표준 출력에 씁니다.

오른쪽 프로세스는 다음을 수행합니다.

  1. 표준 출력을로 리디렉션 tmp하여 프로세스에서 파일을 자릅니다.
  2. head인수를 사용 하여 외부 프로그램 을 실행하십시오 -1.
  3. 표준 입력에서 한 줄을 읽고 표준 출력에 씁니다.

유일한 동기화 지점은 right-3이 left-3이 하나의 전체 회선을 처리 할 때까지 대기한다는 것입니다. left-2와 right-1 사이에는 동기화가 없으므로 어느 순서로든 발생할 수 있습니다. 이들이 발생하는 순서는 예측할 수 없습니다 .CPU 아키텍처, 셸, 커널, 프로세스가 예약되는 코어, 해당 시간 동안 CPU가 수신하는 인터럽트 등에 따라 다릅니다.

행동을 바꾸는 방법

시스템 설정을 변경하여 동작을 변경할 수 없습니다. 컴퓨터가 지시 한대로 수행합니다. 병렬 로 자르고 tmp읽도록 지시 tmp했기 때문에 두 가지를 동시에 수행합니다.

좋아, 변경할 수있는 "시스템 설정"이 하나 있습니다 : /bin/bashbash가 아닌 다른 프로그램으로 대체 할 수 있습니다 . 나는 이것이 좋은 생각이 아니라고 말할 수 있기를 바랍니다.

파이프 왼쪽보다 먼저 잘림을 원하면 파이프 라인 외부에 잘라 내야합니다. 예를 들면 다음과 같습니다.

{ cat tmp | head -1; } >tmp

또는

( exec >tmp; cat tmp | head -1 )

나는 왜 당신이 이것을 원할 지 모른다. 비어있는 파일을 읽을 때 요점은 무엇입니까?

반대로 cat읽기를 마친 후에 출력 리디렉션 (잘림 포함)이 발생 하도록하려면 메모리에 데이터를 완전히 버퍼링해야합니다.

line=$(cat tmp | head -1)
printf %s "$line" >tmp

또는 다른 파일에 쓴 다음 제자리로 이동하십시오. 이것은 일반적으로 스크립트에서 작업을 수행하는 강력한 방법이며 파일이 원래 이름으로 표시되기 전에 전체가 작성된다는 이점이 있습니다.

cat tmp | head -1 >new && mv new tmp

moreutils의 컬렉션 단지라는 것을 수행하는 프로그램을 포함한다 sponge.

cat tmp | head -1 | sponge tmp

자동으로 문제를 감지하는 방법

목표가 잘못 작성된 스크립트를 가져 와서 스크립트가 깨지는 위치를 자동으로 파악하는 것이 목적이라면 인생은 그렇게 간단하지 않습니다. cat잘림이 발생하기 전에 때때로 읽기를 완료하기 때문에 런타임 분석에서 문제를 확실하게 찾을 수 없습니다 . 정적 분석은 원칙적으로 가능합니다. 귀하의 질문에 단순화 된 예는 Shellcheck 에 의해 잡히지 만 더 복잡한 스크립트에서 비슷한 문제를 잡을 수는 없습니다.


그것이 저의 목표였으며, 스크립트가 제대로 작성되었는지 여부를 결정했습니다. 스크립트가 이런 식으로 데이터를 파괴했을 경우 매번 데이터를 파괴하기를 원했습니다. 이것이 거의 불가능하다는 말을 듣는 것은 좋지 않습니다. 덕분에 이제 문제가 무엇인지 알고 해결책을 생각하려고 노력할 것입니다.
karlosss

@karlosss : 흠, 나는 strace(예를 들어 리눅스 와 같은) 시스템 호출 추적 / 가로 채기 ptrace를 사용하여 모든 open읽기 프로세스 호출 (모든 자식 프로세스에서)을 0.5 초 동안 잠들 수 있는지 궁금 합니다 . 잘림, 잘림은 거의 항상 이길 것입니다.
Peter Cordes

@ PeterCordes 나는 이것에 초보자입니다, 당신이 이것을 달성하는 방법을 관리하고 답변으로 쓸 수 있다면, 나는 그것을 받아 들일 것입니다.
karlosss

@PeterCordes 잘림이 지연으로 이길 것이라고 보장 할 수 없습니다. 대부분의 경우 작동하지만 때로는 너무 많이로드 된 컴퓨터에서 스크립트가 다소 신비한 방식으로 실패합니다.
Gilles 'SO- 악한 중지

@Gilles : 내 대답에 따라 이것에 대해 논의 해 보자.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.