Skip to content

Commit c81a6fd

Browse files
[GR-21864] Backport to 20.1.
PullRequest: truffleruby/1603
2 parents e64badc + bc8e500 commit c81a6fd

File tree

12 files changed

+324
-159
lines changed

12 files changed

+324
-159
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Bug fixes:
6464
* Fix issue where interpolated string matched `#` within string as being a variable (#1495).
6565
* Fix `File.join` to raise error on strings with null bytes.
6666
* Fix initialization of Ruby Thread for foreign thread created in Java.
67+
* Thread local IO buffers are now allocated using a stack to ensure safe operating if a signal handler uses one during an IO operation.
6768

6869
Compatibility:
6970

lib/truffle/socket/truffle/foreign.rb

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -96,32 +96,42 @@ module Foreign
9696

9797
def self.bind(descriptor, sockaddr)
9898
sockaddr_p = Truffle::FFI::Pool.stack_alloc(:char, sockaddr.bytesize)
99-
100-
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
101-
102-
_bind(descriptor, sockaddr_p, sockaddr.bytesize)
99+
begin
100+
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
101+
_bind(descriptor, sockaddr_p, sockaddr.bytesize)
102+
ensure
103+
Truffle::FFI::Pool.stack_free(sockaddr_p)
104+
end
103105
end
104106

105107
def self.connect(descriptor, sockaddr)
106108
sockaddr = Socket.coerce_to_string(sockaddr)
107109

108110
sockaddr_p = Truffle::FFI::Pool.stack_alloc(:char, sockaddr.bytesize)
109111

110-
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
112+
begin
113+
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
111114

112-
_connect(descriptor, sockaddr_p, sockaddr.bytesize)
115+
_connect(descriptor, sockaddr_p, sockaddr.bytesize)
116+
ensure
117+
Truffle::FFI::Pool.stack_free(sockaddr_p)
118+
end
113119
end
114120

115121
def self.getsockopt(descriptor, level, optname)
116122
val, length = Truffle::FFI::Pool.stack_alloc(:char, 256, :socklen_t, 1)
117123

118-
length.write_int(256)
124+
begin
125+
length.write_int(256)
119126

120-
err = _getsockopt(descriptor, level, optname, val, length)
127+
err = _getsockopt(descriptor, level, optname, val, length)
121128

122-
Errno.handle('Unable to get socket option') unless err == 0
129+
Errno.handle('Unable to get socket option') unless err == 0
123130

124-
val.read_string(length.read_int)
131+
val.read_string(length.read_int)
132+
ensure
133+
Truffle::FFI::Pool.stack_free(val)
134+
end
125135
end
126136

127137
def self.getaddrinfo(host, service = nil, family = nil, socktype = nil,
@@ -134,6 +144,7 @@ def self.getaddrinfo(host, service = nil, family = nil, socktype = nil,
134144
hints[:ai_flags] = flags || 0
135145

136146
res_p = Truffle::FFI::Pool.stack_alloc(:pointer, 1)
147+
137148
res_p.clear
138149
err = _getaddrinfo(host, service, hints.pointer, res_p)
139150

@@ -172,6 +183,7 @@ def self.getaddrinfo(host, service = nil, family = nil, socktype = nil,
172183

173184
# Be sure to feed a legit pointer to freeaddrinfo
174185
freeaddrinfo(ptr) unless ptr.null?
186+
Truffle::FFI::Pool.stack_free(res_p)
175187
end
176188
end
177189

@@ -188,52 +200,64 @@ def self.getnameinfo(sockaddr, flags = ::Socket::NI_NUMERICHOST | ::Socket::NI_N
188200
sockaddr_p, node, service = Truffle::FFI::Pool.stack_alloc(
189201
:char, sockaddr.bytesize, :char, ::Socket::NI_MAXHOST, :char, ::Socket::NI_MAXSERV)
190202

191-
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
203+
begin
204+
sockaddr_p.write_string(sockaddr, sockaddr.bytesize)
192205

193-
if reverse_lookup
194-
err = _getnameinfo(sockaddr_p, sockaddr.bytesize, node,
195-
::Socket::NI_MAXHOST, nil, 0, 0)
206+
if reverse_lookup
207+
err = _getnameinfo(sockaddr_p, sockaddr.bytesize, node,
208+
::Socket::NI_MAXHOST, nil, 0, 0)
196209

197-
name_info[2] = node.read_string if err == 0
198-
end
210+
name_info[2] = node.read_string if err == 0
211+
end
199212

200-
err = _getnameinfo(sockaddr_p, sockaddr.bytesize, node,
201-
::Socket::NI_MAXHOST, service,
202-
::Socket::NI_MAXSERV, flags)
213+
err = _getnameinfo(sockaddr_p, sockaddr.bytesize, node,
214+
::Socket::NI_MAXHOST, service,
215+
::Socket::NI_MAXSERV, flags)
203216

204-
raise SocketError, gai_strerror(err) unless err == 0
217+
raise SocketError, gai_strerror(err) unless err == 0
205218

206-
sa_family = SockaddrIn.new(sockaddr_p)[:sin_family]
219+
sa_family = SockaddrIn.new(sockaddr_p)[:sin_family]
207220

208-
name_info[0] = ::Socket::Constants::AF_TO_FAMILY[sa_family]
209-
name_info[1] = service.read_string
210-
name_info[3] = node.read_string
221+
name_info[0] = ::Socket::Constants::AF_TO_FAMILY[sa_family]
222+
name_info[1] = service.read_string
223+
name_info[3] = node.read_string
211224

212-
name_info[2] = name_info[3] unless name_info[2]
225+
name_info[2] = name_info[3] unless name_info[2]
213226

214-
name_info
227+
name_info
228+
ensure
229+
Truffle::FFI::Pool.stack_free(sockaddr_p)
230+
end
215231
end
216232

217233
def self.getpeername(descriptor)
218234
sockaddr_storage_p, len_p = Truffle::FFI::Pool.stack_alloc(:char, 128, :socklen_t, 1)
219-
len_p.write_int(128)
235+
begin
236+
len_p.write_int(128)
220237

221-
err = _getpeername(descriptor, sockaddr_storage_p, len_p)
238+
err = _getpeername(descriptor, sockaddr_storage_p, len_p)
222239

223-
Errno.handle('getpeername(2)') unless err == 0
240+
Errno.handle('getpeername(2)') unless err == 0
224241

225-
sockaddr_storage_p.read_string(len_p.read_int)
242+
sockaddr_storage_p.read_string(len_p.read_int)
243+
ensure
244+
Truffle::FFI::Pool.stack_free(sockaddr_storage_p)
245+
end
226246
end
227247

228248
def self.getsockname(descriptor)
229249
sockaddr_storage_p, len_p = Truffle::FFI::Pool.stack_alloc(:char, 128, :socklen_t, 1)
230250

231-
len_p.write_int(128)
232-
err = _getsockname(descriptor, sockaddr_storage_p, len_p)
251+
begin
252+
len_p.write_int(128)
253+
err = _getsockname(descriptor, sockaddr_storage_p, len_p)
233254

234-
Errno.handle('getsockname(2)') unless err == 0
255+
Errno.handle('getsockname(2)') unless err == 0
235256

236-
sockaddr_storage_p.read_string(len_p.read_int)
257+
sockaddr_storage_p.read_string(len_p.read_int)
258+
ensure
259+
Truffle::FFI::Pool.stack_free(sockaddr_storage_p)
260+
end
237261
end
238262

239263
def self.pack_sockaddr_in(host, port, family = ::Socket::AF_UNSPEC,
@@ -278,6 +302,7 @@ def self.pack_sockaddr_in(host, port, family = ::Socket::AF_UNSPEC,
278302
ptr = res_p.read_pointer
279303

280304
freeaddrinfo(ptr) unless ptr.null?
305+
Truffle::FFI::Pool.stack_free(res_p)
281306
end
282307
end
283308

@@ -304,12 +329,16 @@ def self.getpeereid(*)
304329

305330
def self.socketpair(family, type, protocol)
306331
pointer = Truffle::FFI::Pool.stack_alloc(:int, 2)
307-
pointer.clear
308-
status = _socketpair(family, type, protocol, pointer)
332+
begin
333+
pointer.clear
334+
status = _socketpair(family, type, protocol, pointer)
309335

310-
Errno.handle('socketpair(2)') unless status == 0
336+
Errno.handle('socketpair(2)') unless status == 0
311337

312-
pointer.read_array_of_int(2)
338+
pointer.read_array_of_int(2)
339+
ensure
340+
Truffle::FFI::Pool.stack_free(pointer)
341+
end
313342
end
314343

315344
def self.char_pointer(length, &block)
@@ -346,11 +375,15 @@ def self.ip_to_bytes(family, address)
346375

347376
pointer = Truffle::FFI::Pool.stack_alloc(:pointer, size)
348377

349-
status = inet_pton(family, address, pointer)
378+
begin
379+
status = inet_pton(family, address, pointer)
350380

351-
Errno.handle('inet_pton()') if status < 1
381+
Errno.handle('inet_pton()') if status < 1
352382

353-
pointer.get_array_of_uchar(0, size)
383+
pointer.get_array_of_uchar(0, size)
384+
ensure
385+
Truffle::FFI::Pool.stack_free(pointer)
386+
end
354387
end
355388
end
356389
end

src/main/java/org/truffleruby/core/support/IONodes.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import org.truffleruby.core.rope.RopeOperations;
8383
import org.truffleruby.core.string.StringNodes.MakeStringNode;
8484
import org.truffleruby.core.thread.GetCurrentRubyThreadNode;
85+
import org.truffleruby.core.thread.ThreadLocalBuffer;
8586
import org.truffleruby.core.thread.ThreadManager.BlockingAction;
8687
import org.truffleruby.extra.ffi.Pointer;
8788
import org.truffleruby.language.Visibility;
@@ -480,8 +481,8 @@ protected int write(int fd, DynamicObject string) {
480481

481482
}
482483

483-
@Primitive(name = "io_get_thread_buffer")
484-
public static abstract class GetThreadBufferNode extends PrimitiveArrayArgumentsNode {
484+
@Primitive(name = "io_thread_buffer_allocate")
485+
public static abstract class IOThreadBufferAllocateNode extends PrimitiveArrayArgumentsNode {
485486

486487
@Specialization
487488
protected DynamicObject getThreadBuffer(VirtualFrame frame, long size,
@@ -496,23 +497,27 @@ protected DynamicObject getThreadBuffer(VirtualFrame frame, long size,
496497
}
497498

498499
public static Pointer getBuffer(DynamicObject rubyThread, long size, ConditionProfile sizeProfile) {
499-
final Pointer buffer = Layouts.THREAD.getIoBuffer(rubyThread);
500-
501-
if (sizeProfile.profile(buffer.getSize() >= size)) {
502-
return buffer;
503-
} else {
504-
return reallocateBuffer(size, rubyThread, buffer);
505-
}
506-
}
507-
508-
@TruffleBoundary
509-
private static Pointer reallocateBuffer(long size, final DynamicObject rubyThread, final Pointer buffer) {
510-
buffer.freeNoAutorelease();
511-
final Pointer newBuffer = Pointer.malloc(Math.max(size * 2, 1024));
500+
final ThreadLocalBuffer buffer = Layouts.THREAD.getIoBuffer(rubyThread);
501+
final ThreadLocalBuffer newBuffer = buffer.allocate(size, sizeProfile);
512502
Layouts.THREAD.setIoBuffer(rubyThread, newBuffer);
513-
return newBuffer;
503+
return newBuffer.start;
514504
}
515505

516506
}
517507

508+
@Primitive(name = "io_thread_buffer_free")
509+
public static abstract class IOThreadBufferFreeNode extends PrimitiveArrayArgumentsNode {
510+
511+
@Specialization
512+
protected Object getThreadBuffer(VirtualFrame frame, DynamicObject pointer,
513+
@Cached GetCurrentRubyThreadNode currentThreadNode,
514+
@Cached("createBinaryProfile()") ConditionProfile freeProfile) {
515+
DynamicObject thread = currentThreadNode.executeGetRubyThread(frame);
516+
final ThreadLocalBuffer threadBuffer = Layouts.THREAD.getIoBuffer(thread);
517+
assert threadBuffer.start.getAddress() == Layouts.POINTER.getPointer(pointer).getAddress();
518+
Layouts.THREAD.setIoBuffer(thread, threadBuffer.free(freeProfile));
519+
return nil;
520+
}
521+
}
522+
518523
}

src/main/java/org/truffleruby/core/thread/ThreadLayout.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.truffleruby.core.InterruptMode;
1818
import org.truffleruby.core.basicobject.BasicObjectLayout;
1919
import org.truffleruby.core.fiber.FiberManager;
20-
import org.truffleruby.extra.ffi.Pointer;
2120
import org.truffleruby.language.threadlocal.ThreadLocalGlobals;
2221

2322
import com.oracle.truffle.api.object.DynamicObject;
@@ -57,7 +56,7 @@ Object[] build(
5756
@Nullable @Volatile Object value,
5857
AtomicBoolean wakeUp,
5958
@Volatile int priority,
60-
Pointer ioBuffer,
59+
ThreadLocalBuffer ioBuffer,
6160
Object threadGroup,
6261
String sourceLocation,
6362
Object name);
@@ -112,9 +111,9 @@ Object[] build(
112111

113112
void setPriority(DynamicObject object, int value);
114113

115-
Pointer getIoBuffer(DynamicObject object);
114+
ThreadLocalBuffer getIoBuffer(DynamicObject object);
116115

117-
void setIoBuffer(DynamicObject object, Pointer value);
116+
void setIoBuffer(DynamicObject object, ThreadLocalBuffer value);
118117

119118
Object getThreadGroup(DynamicObject object);
120119

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.core.thread;
11+
12+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
13+
import com.oracle.truffle.api.profiles.ConditionProfile;
14+
15+
import org.truffleruby.extra.ffi.Pointer;
16+
17+
public final class ThreadLocalBuffer {
18+
19+
public static final ThreadLocalBuffer NULL_BUFFER = new ThreadLocalBuffer(true, Pointer.NULL, 0, 0, null);
20+
21+
final boolean ownsBuffer;
22+
public final Pointer start;
23+
final long used;
24+
final long remaining;
25+
final ThreadLocalBuffer parent;
26+
27+
private ThreadLocalBuffer(
28+
boolean isBlockStart,
29+
Pointer start,
30+
long used,
31+
long remaining,
32+
ThreadLocalBuffer parent) {
33+
this.ownsBuffer = isBlockStart;
34+
this.start = start;
35+
this.used = used;
36+
this.remaining = remaining;
37+
this.parent = parent;
38+
}
39+
40+
public ThreadLocalBuffer free(ConditionProfile freeProfile) {
41+
if (freeProfile.profile(ownsBuffer)) {
42+
start.freeNoAutorelease();
43+
}
44+
return parent;
45+
}
46+
47+
public void freeAll() {
48+
ThreadLocalBuffer current = this;
49+
while (current != null) {
50+
current.free(ConditionProfile.getUncached());
51+
current = current.parent;
52+
}
53+
}
54+
55+
public ThreadLocalBuffer allocate(long size, ConditionProfile allocationProfile) {
56+
if (allocationProfile.profile(remaining >= size)) {
57+
return new ThreadLocalBuffer(
58+
false,
59+
new Pointer(this.start.getAddress() + this.used, size),
60+
size,
61+
remaining - size,
62+
this);
63+
} else {
64+
return allocateNewBlock(size);
65+
}
66+
}
67+
68+
@TruffleBoundary
69+
private ThreadLocalBuffer allocateNewBlock(long size) {
70+
// Allocate a new buffer. Chain it if we aren't the default thread buffer, otherwise make a new default buffer.
71+
final long blockSize = Math.max(size, 1024);
72+
if (this.parent != null) {
73+
return new ThreadLocalBuffer(true, Pointer.malloc(blockSize), size, blockSize - size, this);
74+
} else {
75+
// Free the old block
76+
this.free(ConditionProfile.getUncached());
77+
// Create new bigger block
78+
final ThreadLocalBuffer newParent = new ThreadLocalBuffer(
79+
true,
80+
Pointer.malloc(blockSize),
81+
0,
82+
blockSize,
83+
null);
84+
return new ThreadLocalBuffer(
85+
false,
86+
new Pointer(newParent.start.getAddress(), size),
87+
size,
88+
blockSize - size,
89+
newParent);
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)