OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-sensory / tests / sensory / filedir / textfile-thread-test.scm

148 lines · scm ref:
#! /usr/bin/env guile
-s
!#
;
; textfile-thread-test.scm -- Test TextFileNode thread safety
;
; Tests that TextFileNode can be safely closed from one thread while
; another thread is blocked waiting for data in follow (tail) mode.
; This tests proper cleanup and unblocking behavior.
;
(use-modules (opencog) (opencog exec) (opencog sensory))
(use-modules (opencog test-runner))
(use-modules (ice-9 threads))

(opencog-test-runner)

(define tname "textfile-thread")
(test-begin tname)

; Create a test file with initial content
(define test-file "/tmp/textfile-thread-test.txt")
(with-output-to-file test-file
	(lambda () (display "line1\nline2\n")))

; ----------------------------------------------------------
; Test 1: Close from another Scheme thread while blocked

(define file-node-1 (TextFile (string-append "file://" test-file)))

; Open and enable follow mode
(cog-execute! (SetValue file-node-1 (Predicate "*-open-*") (Type 'StringValue)))
(cog-set-value! file-node-1 (Predicate "*-follow-*") (BoolValue #t))

; Read the two existing lines
(define line1 (cog-execute! (ValueOf file-node-1 (Predicate "*-read-*"))))
(define line2 (cog-execute! (ValueOf file-node-1 (Predicate "*-read-*"))))

(test-assert "read-initial-lines"
	(and (equal? 'StringValue (cog-type line1))
	     (equal? 'StringValue (cog-type line2))))

; Create reader thread that will block waiting for more data
(define reader-result #f)
(define reader-exception #f)

(define reader-thread
	(call-with-new-thread
		(lambda ()
			(catch #t
				(lambda ()
					(set! reader-result
						(cog-execute! (ValueOf file-node-1 (Predicate "*-read-*")))))
				(lambda (key . args)
					(set! reader-exception (cons key args)))))))

; Give reader thread time to enter blocked state
(usleep 500000) ; 500ms

; Now close from main thread - this should unblock the reader
(cog-set-value! file-node-1 (Predicate "*-close-*") (VoidValue))

; Wait for reader thread to complete (should return immediately now)
(define join-result (join-thread reader-thread))

; The reader should have received VoidValue (empty) when closed
(test-assert "scheme-thread-unblocked"
	(or (and reader-result (equal? 'VoidValue (cog-type reader-result)))
	    reader-exception)) ; Either got VoidValue or caught exception is OK

; ----------------------------------------------------------
; Test 2: Close from another thread using ExecuteThreadedLink (pure Atomese)

(define file-node-2 (TextFile (string-append "file://" test-file)))

; Rewrite test file
(with-output-to-file test-file
	(lambda () (display "line1\nline2\n")))

; Open and enable follow mode
(cog-execute! (SetValue file-node-2 (Predicate "*-open-*") (Type 'StringValue)))
(cog-set-value! file-node-2 (Predicate "*-follow-*") (BoolValue #t))

; Read the two existing lines first
(cog-execute! (ValueOf file-node-2 (Predicate "*-read-*")))
(cog-execute! (ValueOf file-node-2 (Predicate "*-read-*")))

; Create pure Atomese threaded execution:
; Thread 1: Try to read (will block)
; Thread 2: Sleep then close
(define threaded-test
	(ExecuteThreadedLink
		; Thread 1: Reader - will block waiting for data
		(ValueOf file-node-2 (Predicate "*-read-*"))

		; Thread 2: Closer - sleeps then closes (pure Atomese)
		(SequentialAnd
			; Sleep to let reader block first
			(Sleep (Number 0.5))
			; Then close to unblock reader (using Concept as close signal)
			(SetValue file-node-2 (Predicate "*-close-*") (Concept "close-signal")))))

; Execute the threaded test
; This should complete without hanging
(define threaded-result #f)
(define threaded-exception #f)

(catch #t
	(lambda ()
		(set! threaded-result (cog-execute! threaded-test)))
	(lambda (key . args)
		(set! threaded-exception (cons key args))))

; Test should complete (not hang)
(test-assert "atomese-thread-completed"
	(or threaded-result threaded-exception))

; If we got a result, it should be a QueueValue with results from both threads
(test-assert "atomese-thread-result-structure"
	(or (and threaded-result
	         (equal? 'QueueValue (cog-type threaded-result)))
	    threaded-exception)) ; Exception is acceptable

; ----------------------------------------------------------
; Test 3: Verify no corruption after threaded close

(define file-node-3 (TextFile (string-append "file://" test-file)))

; Should be able to open again without issues
(cog-execute! (SetValue file-node-3 (Predicate "*-open-*") (Type 'StringValue)))

; Read a line normally
(define normal-read (cog-execute! (ValueOf file-node-3 (Predicate "*-read-*"))))

(test-assert "no-corruption-after-threaded-close"
	(equal? 'StringValue (cog-type normal-read)))

; Clean close
(cog-set-value! file-node-3 (Predicate "*-close-*") (VoidValue))

; ----------------------------------------------------------
; Clean up

(system (string-append "rm -f " test-file))

(test-end tname)

(opencog-test-end)