Ruby Check Whether Program Is Currently Being Closed

Ruby check whether program is currently being closed

See Below taken from my answer Here but seems more pertinent to your question than the one it is currently attached to:

Your best bet is probably a bit easier than signal trapping. The Kernel Module actually offers you an #at_exit method that will be executed just prior to the program actually exiting.

Usage: (from Kernel#at_exit Docs)

def do_at_exit(str1)
at_exit { print str1 }
end
at_exit { puts "cruel world" }
do_at_exit("goodbye ")
exit

"produces:"

goodbye cruel world

as you can see you can define multiple handlers which will be executed in reverse order when the program exits.

Since Kernel is included in Object you can handle Object specifics as well like

class People
at_exit {puts "The #{self.name} have left"}
end
exit
# The People have left

or even on instances

p = People.new
p.send(:at_exit, &->{puts "We are leaving"})
# We are leaving
# The People have left

Additionally for more specific Object based implementations you can take a look at ObjectSpace.define_finalizer.

example of usage:

class Person
def self.finalize(name)
proc {puts "Goodbye Cruel World -#{name}"}
end
def initialize(name)
@name = name
ObjectSpace.define_finalizer(self, self.class.finalize(@name))
end
end

Usage:

p = Person.new("engineersmnky")
exit
# Goodbye Cruel World -engineersmnky

This may not be specifically what you want as this will fire when an Object is garbage collected as well (not great for ephemeral objects) but if you have objects that should exist throughout the entire application this could still be used similar to an at_exit . Example

# requiring WeakRef to allow garbage collection 
# See: https://ruby-doc.org/stdlib-2.3.3/libdoc/weakref/rdoc/WeakRef.html
require 'weakref' #
p1 = Person.new("Engineer")
p2 = Person.new("Engineer's Monkey")
p2 = WeakRef.new(p2)
GC.start # just for this example
# Goodbye Cruel World -Engineer's Monkey
#=> nil
p2
#=> WeakRef::RefError: Invalid Reference - probably recycled
exit
# Goodbye Cruel World -Engineer

As you can see the defined finalizer for p2 fired because the Person was gc'd but the program has not exited yet. p1's finalizer waited until exit to fire because it retained its reference throughout the application.

Ruby: how to check if a file is still open?

Reasonable (=all) OSs close fds automatically when a program ends, no matter what the code does.

As for ruby code trying to be nice during execution, Ruby has finalizers that autoclose when a variable that used to reference a filedescriptor (or an encapsulated filedescriptor) gets garbage-collected.

On a UNIX system, you can check open files with lsof.

The code below demonstrates the concepts:

rb.rb:

def func()
fd = IO.sysopen("file.txt", "a")
myios = IO.new(fd)
myios.puts "new line"
end
func
sleep 3 #most likely open here unless the GC managed to run
GC.start #should be closed after this point
sleep 3

Now if you invoke it with:

$ ruby rb.rb  & pid=$!; while kill -0 $pid; do if lsof -p $pid | grep -q file.txt; then echo open; else echo closed; fi; sleep 0.3; done

you'll probably get one "closed" (before the ruby code catches up), 3 seconds of open and then 3 seconds of closed.

If you don't want to rely on finalizers (which are will run at indeterministic times, because they rely on the garbage collecter), then the block syntax for opening files is really nice in ruby -- the end of the block will deterministically close the file at the very point where the block ends.

How to check if the file is still locked by current thread?

If f.flock(File::LOCK_EX | File::LOCK_NB) returns non false value then f IS locked. It will keep the lock until you close the file or explicitly call f.flock(File::LOCK_UN). You don't have to check whether it is locked again. To explain what really happens there we need to look into a file system internals and related system calls first:

 File Descriptor Table       Open File Table        i-node Table      Directory Index
╒════════════════════╕ ╒═════════════╕ ╒════════════╕ ╒═════════════╕
┃3 ..................┣━━━━━━▷┃ open file1 ┣━━┳━━━▷┃ /tmp/file1 ┃◃━━━━┫ file1 ┃
┃4 ..................┣━━━━━━▷┃ open file1 ┣━━┚ ┏━▷┃ /tmp/file2 ┃◃━━━━┫ file2 ┃
┃5 ..................┣━━━┳━━▷┃ open file2 ┣━━━━┚
┃6 ..................┣━━━┚

The key point in this diagram is that there are two different and unrelated entry points into the i-node Table: Open File Table and Directory Index. Different system calls work with different entry points:

  • open(file_path) => finds i-node number from Directory Index and creates an entry in Open File Table referenced by File Descriptor Table (one table per process), then increments ref_counter in the related i-node Table entry.
  • close(file_descriptor) => closes (frees) related File Descriptor Table entry and related entry from Open File Table (unless there are other referencing File Descriptors), then decrements ref_counter in related i-node Table entry (unless Open File entry stays open)
  • unlink(file_path) => there is no Delete system call! Unlinks i-node Table from Directory Index by removing entry from Directory Index. Decrements counter in the related i-node Table entry (unaware of Open File Table!)
  • flock(file_desriptor) => apply/remove lock on entries in Open File Table (unaware of Directory Index!)
  • i-node Table entry is removed (practically deleting a file) IFF ref_counter becomes Zero. It can happen after close() or after unlink()

The key point here is that unlink not necessarily deletes a file(the data) immediately! It only unlinks Directory Index and i-node Table. It means that even after unlink the file may still be open with active locks on it!

Keeping that in mind, imagine the following scenario with 2 threads, trying to synchronise on a file using open/flock/close and trying to cleanup using unlink:

   THREAD 1                              THREAD 2
==================================================
| |
| |
(1) OPEN (file1, CREATE) |
| (1) OPEN (file1, CREATE)
| |
(2) LOCK-EX (FD1->i-node-1) |
[start work] (2) LOCK-EX (FD2->i-node-1) <---
| . |
| . |
(3) work . |
| (3) waiting loop |
| . |
[end work] . |
(4) UNLINK (file1) . -----------------------
(5) CLOSE (FD1)--------unlocked------> [start work]
| |
| |
(6) OPEN (file1, CREATE) |
| |
| (5) work
(7) LOCK-EX (FD1->i-node-2) |
[start work] !!! does not wait |
| |
(8) work |
| |
  • (1) both threads open(potentially create) the same file. As a result there is a link from Directory Index to i-node Table. Each thread gets its own File Descriptor.
  • (2) both threads try to get an exclusive lock using File Descriptor they get from an open call
  • (3) first thread gets a lock and second thread is blocked (or is trying to get a lock in a loop)
  • (4) first thread finishes a task and deletes (unlink) a file. At this point link from Directory Index to i-node is removed and we won't see it in the directory listing. BUT, the file is still there and is open in two threads with an active lock! It simply lost its name.
  • (5) first thread closes File Descriptor and as a result releases a lock. Thus second thread gets a lock and starts working on a task
  • (6) first thread repeats and tries to open a file with the same name. But is it the same file as before? No. Because at this point there is no file with a given name in Directory Index. So it creates a NEW file instead! new i-node Table entry.
  • (7) first thread gets a lock on a NEW file!
  • (8) and we get two threads with a lock on two different files and UNsynchronised

The problem in the above scenario is that open/unlink work on Directory Index, while lock/close work on File Descriptors, which are not related to each other.

To solve this issue we need to synchronise these operations through some central entry point. It can be implemented by introducing a singleton service which will provide this synchronisation using a Mutex or primitives from Concurrent Ruby.

Here is one possible PoC implementation:

class FS
include Singleton

def initialize
@mutex = Mutex.new
@files = {}
end

def open(path)
path = File.absolute_path(path)
file = nil
@mutex.synchronize do
file = File.open(path, File::CREAT | File::RDWR)
ref_count = @files[path] || 0
@files[path] = ref_count + 1
end

yield file
ensure
@mutex.synchronize do
file.close
ref_count = @files[path] - 1
if ref_count.zero?
FileUtils.rm(path, force: true)
@files.delete(path)
else
@files[path] = ref_count
end
end
end
end

And here is your re-written example from the question:

FS.instance.open('a.txt') do |f|
if f.flock(File::LOCK_EX | File::LOCK_NB)
# you can be sure that you have a lock
end
# 'a.txt' will finally be deleted
end

Check if socket-client is still connected

I don't have experience programming ruby. If it was .Net, what you pretend is not possible without some kind of keep-alive protocol. If the server does not get data from client after x seconds it assumes the client disconnected. The client must have a timer to send something to the server when not communicating.

How do I let the user know whether X or O won?

I recommend that you create a Board class that knows how to draw itself based on the turns. This would remove a lot of the code from Game and make it easier to follow.

To answer your question, the code you have here:

                    if str == "xxx" or str == "ooo"
return true
end

doesn't distinguish between X winning or O winning. Here you can modify the code to check separately for X win and O win, and display the appropriate message.

How can I launch a application with Ruby?

Run an application in various ways:

Kernel#system runs my_program in a subshell and returns true if the subshell exits successfully, false otherwise.

system("my_program")

Kernel#exec replaces the currently executing process with my_program.

exec("my_program")

%x() will run the program and return the output.

%x(my_program)



Related Topics



Leave a reply



Submit