From 0e332f4888013555e95614d942fa07d12aa4ce10 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 17:05:51 -0500 Subject: [PATCH 1/8] Clean up compile warnings on 22 Warnings about release=8 can't be avoided at the moment. JRuby 10 will bump minimum up to 17 or 21 and we will deal with bumping the Java version for StringIO then. --- ext/java/org/jruby/ext/stringio/StringIO.java | 76 +++++++++---------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index d1d50c7..f217d06 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -64,6 +64,7 @@ import static org.jruby.runtime.Visibility.PRIVATE; @JRubyClass(name="StringIO") +@SuppressWarnings("serial") public class StringIO extends RubyObject implements EncodingCapable, DataType { static class StringIOData { /** @@ -598,60 +599,52 @@ public IRubyObject gets(ThreadContext context, IRubyObject[] args) { } } - private static final Getline.Callback GETLINE = new Getline.Callback() { - @Override - public IRubyObject getline(ThreadContext context, StringIO self, IRubyObject rs, int limit, boolean chomp, Block block) { - if (limit == 0) { - return RubyString.newEmptyString(context.runtime, self.getEncoding()); - } + private static final Getline.Callback GETLINE = (context, self, rs, limit, chomp, block) -> { + if (limit == 0) { + return RubyString.newEmptyString(context.runtime, self.getEncoding()); + } - if (rs.isNil()) chomp = false; + if (rs.isNil()) chomp = false; - IRubyObject result = self.getline(context, rs, limit, chomp); + IRubyObject result = self.getline(context, rs, limit, chomp); - context.setLastLine(result); + context.setLastLine(result); - return result; - } + return result; }; - private static final Getline.Callback GETLINE_YIELD = new Getline.Callback() { - @Override - public StringIO getline(ThreadContext context, StringIO self, IRubyObject rs, int limit, boolean chomp, Block block) { - IRubyObject line; + private static final Getline.Callback GETLINE_YIELD = (context, self, rs, limit, chomp, block) -> { + IRubyObject line; - if (limit == 0) { - throw context.runtime.newArgumentError("invalid limit: 0 for each_line"); - } - - if (rs.isNil()) chomp = false; + if (limit == 0) { + throw context.runtime.newArgumentError("invalid limit: 0 for each_line"); + } - while (!(line = self.getline(context, rs, limit, chomp)).isNil()) { - block.yieldSpecific(context, line); - } + if (rs.isNil()) chomp = false; - return self; + while (!(line = self.getline(context, rs, limit, chomp)).isNil()) { + block.yieldSpecific(context, line); } - }; - private static final Getline.Callback GETLINE_ARY = new Getline.Callback() { - @Override - public RubyArray getline(ThreadContext context, StringIO self, IRubyObject rs, int limit, boolean chomp, Block block) { - RubyArray ary = context.runtime.newArray(); - IRubyObject line; + return self; + }; - if (limit == 0) { - throw context.runtime.newArgumentError("invalid limit: 0 for readlines"); - } + private static final Getline.Callback> GETLINE_ARY = (context, self, rs, limit, chomp, block) -> { + @SuppressWarnings("unchecked") + RubyArray ary = (RubyArray) context.runtime.newArray(); + IRubyObject line; - if (rs.isNil()) chomp = false; + if (limit == 0) { + throw context.runtime.newArgumentError("invalid limit: 0 for readlines"); + } - while (!(line = self.getline(context, rs, limit, chomp)).isNil()) { - ary.append(line); - } + if (rs.isNil()) chomp = false; - return ary; + while (!(line = self.getline(context, rs, limit, chomp)).isNil()) { + ary.append(line); } + + return ary; }; // strio_getline @@ -850,6 +843,7 @@ public IRubyObject putc(ThreadContext context, IRubyObject ch) { public static final ByteList NEWLINE = ByteList.create("\n"); @JRubyMethod(name = "read", optional = 2) + @SuppressWarnings("fallthrough") public IRubyObject read(ThreadContext context, IRubyObject[] args) { checkReadable(); @@ -928,6 +922,7 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { } @JRubyMethod(name = "pread", required = 2, optional = 1) + @SuppressWarnings("fallthrough") public IRubyObject pread(ThreadContext context, IRubyObject[] args) { checkReadable(); @@ -1512,7 +1507,8 @@ public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRuby if (!args[i].isNil()) { IRubyObject tmp = args[i].checkArrayType(); if (!tmp.isNil()) { - RubyArray arr = (RubyArray) tmp; + @SuppressWarnings("unchecked") + RubyArray arr = (RubyArray) tmp; if (runtime.isInspecting(arr)) { line = runtime.newString("[...]"); } else { @@ -1538,7 +1534,7 @@ public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRuby return runtime.getNil(); } - private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeIO, RubyArray array) { + private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeIO, RubyArray array) { Ruby runtime = context.runtime; try { runtime.registerInspecting(array); From c4a8d375aef2e175bd974ab3944b607ce2762f9a Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 17:14:38 -0500 Subject: [PATCH 2/8] JRuby errors during pos= rather than lazily Being based on JVM, JRuby cannot support a byte[] size larger than a signed in, and it checks that the position is within that range at the time it is set rather than when it is used by other operations. This patch moves the position set into the assertion so it will pass on both JRuby and other implementations. --- test/stringio/test_stringio.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 8afbcf3..ae8e845 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -237,8 +237,9 @@ def test_write_encoding_conversion def test_write_integer_overflow f = StringIO.new - f.pos = RbConfig::LIMITS["LONG_MAX"] assert_raise(ArgumentError) { + # JRuby errors when setting pos to an out-of-range value + f.pos = RbConfig::LIMITS["LONG_MAX"] f.write("pos + len overflows") } end From d5816926d546231312af8dc7df043c47d7f22e73 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 21:24:48 -0500 Subject: [PATCH 3/8] Replace synchronization with a spin lock All of these operations are leaves, so no reentrancy is necessary. None of them make any blocking calls, either, so they should return very quickly. And use of StringIO under concurrent load is very unusual and unlikely to lead to contention. This patch switches from using hard JVM synchronization to instead just spin- lock on an atomic int value associated with the data ptr object. Performance on a benchmark of the Prism parser from ruby/prism#2358 shows a reduction per loop from 0.050s to 0.044s. --- ext/java/org/jruby/ext/stringio/StringIO.java | 116 ++++++++++++++---- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index f217d06..48400d9 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -59,6 +59,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static org.jruby.RubyEnumerator.enumeratorize; import static org.jruby.runtime.Visibility.PRIVATE; @@ -76,6 +77,7 @@ static class StringIOData { int pos; int lineno; int flags; + volatile int locked; } StringIOData ptr; @@ -86,6 +88,8 @@ static class StringIOData { private static final int STRIO_WRITABLE = ObjectFlags.registry.newFlag(StringIO.class); private static final int STRIO_READWRITE = (STRIO_READABLE | STRIO_WRITABLE); + private static final AtomicIntegerFieldUpdater LOCKED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(StringIOData.class, "locked"); + public static RubyClass createStringIOClass(final Ruby runtime) { RubyClass stringIOClass = runtime.defineClass( "StringIO", runtime.getObject(), StringIO::new); @@ -167,7 +171,8 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { int argc = args.length; Encoding encoding = null; @@ -219,6 +224,8 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { ptr.lineno = 0; // funky way of shifting readwrite flags into object flags flags |= (ptr.flags & OpenFile.READWRITE) * (STRIO_READABLE / OpenFile.READABLE); + } finally { + unlock(ptr); } } @@ -440,7 +447,8 @@ public IRubyObject each_byte(ThreadContext context, Block block) { Ruby runtime = context.runtime; StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ByteList bytes = ptr.string.getByteList(); // Check the length every iteration, since @@ -448,6 +456,8 @@ public IRubyObject each_byte(ThreadContext context, Block block) { while (ptr.pos < bytes.length()) { block.yield(context, runtime.newFixnum(bytes.get(ptr.pos++) & 0xFF)); } + } finally { + unlock(ptr); } return this; @@ -490,13 +500,16 @@ public IRubyObject getc(ThreadContext context) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { int start = ptr.pos; int total = 1 + StringSupport.bytesToFixBrokenTrailingCharacter(ptr.string.getByteList(), start + 1); ptr.pos += total; return context.runtime.newString(ptr.string.getByteList().makeShared(start, total)); + } finally { + unlock(ptr); } } @@ -508,28 +521,30 @@ public IRubyObject getbyte(ThreadContext context) { int c; StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { c = ptr.string.getByteList().get(this.ptr.pos++) & 0xFF; + } finally { + unlock(ptr); } return context.runtime.newFixnum(c); } + // must be called under lock private RubyString strioSubstr(Ruby runtime, int pos, int len, Encoding enc) { StringIOData ptr = this.ptr; - synchronized (ptr) { - final RubyString string = ptr.string; - final ByteList stringBytes = string.getByteList(); - int rlen = string.size() - pos; + final RubyString string = ptr.string; + final ByteList stringBytes = string.getByteList(); + int rlen = string.size() - pos; - if (len > rlen) len = rlen; - if (len < 0) len = 0; + if (len > rlen) len = rlen; + if (len < 0) len = 0; - if (len == 0) return RubyString.newEmptyString(runtime, enc); - string.setByteListShared(); // we only share the byte[] buffer but its easier this way - return RubyString.newStringShared(runtime, stringBytes.getUnsafeBytes(), stringBytes.getBegin() + pos, len, enc); - } + if (len == 0) return RubyString.newEmptyString(runtime, enc); + string.setByteListShared(); // we only share the byte[] buffer but its easier this way + return RubyString.newStringShared(runtime, stringBytes.getUnsafeBytes(), stringBytes.getBegin() + pos, len, enc); } private static final int CHAR_BIT = 8; @@ -664,7 +679,8 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim StringIOData ptr = this.ptr; Encoding enc = getEncoding(); - synchronized (ptr) { + lock(ptr); + try { final ByteList string = ptr.string.getByteList(); final byte[] stringBytes = string.getUnsafeBytes(); int begin = string.getBegin(); @@ -745,6 +761,8 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim } ptr.pos = e - begin; ptr.lineno++; + } finally { + unlock(ptr); } return str; @@ -802,7 +820,8 @@ public IRubyObject set_pos(IRubyObject arg) { private void strioExtend(int pos, int len) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { final int olen = ptr.string.size(); if (pos + len > olen) { ptr.string.resize(pos + len); @@ -818,6 +837,8 @@ private void strioExtend(int pos, int len) { } else { ptr.string.modify19(); } + } finally { + unlock(ptr); } } @@ -855,7 +876,8 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { StringIOData ptr = this.ptr; final RubyString string; - synchronized (ptr) { + lock(ptr); + try { switch (args.length) { case 2: str = args[1]; @@ -916,6 +938,8 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { } } ptr.pos += string.size(); + } finally { + unlock(ptr); } return string; @@ -960,7 +984,8 @@ public IRubyObject pread(ThreadContext context, IRubyObject[] args) { throw runtime.newArgumentError(args.length, 0, 2); } - synchronized (ptr) { + lock(ptr); + try { if (offset >= ptr.string.size()) { throw context.runtime.newEOFError(); } @@ -979,6 +1004,8 @@ public IRubyObject pread(ThreadContext context, IRubyObject[] args) { byte[] dataBytes = dataByteList.getUnsafeBytes(); System.arraycopy(dataBytes, dataByteList.getBegin() + offset, strBytes, strByteList.getBegin(), len); string.setEncoding(ASCIIEncoding.INSTANCE); + } finally { + unlock(ptr); } return string; @@ -1042,9 +1069,12 @@ public IRubyObject rewind(ThreadContext context) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ptr.pos = 0; ptr.lineno = 0; + } finally { + unlock(ptr); } return RubyFixnum.zero(context.runtime); @@ -1068,7 +1098,8 @@ public IRubyObject seek(ThreadContext context, IRubyObject[] args) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { switch (whence.isNil() ? 0 : RubyNumeric.num2int(whence)) { case 0: break; @@ -1085,6 +1116,8 @@ public IRubyObject seek(ThreadContext context, IRubyObject[] args) { if (offset < 0) throw runtime.newErrnoEINVALError("invalid seek value"); ptr.pos = offset; + } finally { + unlock(ptr); } return RubyFixnum.zero(runtime); @@ -1095,13 +1128,16 @@ public IRubyObject set_string(IRubyObject arg) { checkFrozen(); StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ptr.flags &= ~OpenFile.READWRITE; RubyString str = arg.convertToString(); ptr.flags = str.isFrozen() ? OpenFile.READABLE : OpenFile.READWRITE; ptr.pos = 0; ptr.lineno = 0; return ptr.string = str; + } finally { + unlock(ptr); } } @@ -1132,7 +1168,8 @@ public IRubyObject truncate(IRubyObject len) { StringIOData ptr = this.ptr; RubyString string = ptr.string; - synchronized (ptr) { + lock(ptr); + try { int plen = string.size(); if (l < 0) { throw getRuntime().newErrnoEINVALError("negative legnth"); @@ -1143,6 +1180,8 @@ public IRubyObject truncate(IRubyObject len) { // zero the gap Arrays.fill(buf.getUnsafeBytes(), buf.getBegin() + plen, buf.getBegin() + l, (byte) 0); } + } finally { + unlock(ptr); } return len; @@ -1183,7 +1222,8 @@ public IRubyObject ungetc(ThreadContext context, IRubyObject arg) { private void ungetbyteCommon(int c) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ptr.string.modify(); ptr.pos--; @@ -1197,6 +1237,8 @@ private void ungetbyteCommon(int c) { } else { bytes.set(ptr.pos, c); } + } finally { + unlock(ptr); } } @@ -1212,7 +1254,8 @@ private void ungetbyteCommon(byte[] ungetBytes, int ungetBegin, int ungetLen) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ptr.string.modify(); if (ungetLen > ptr.pos) { @@ -1228,6 +1271,8 @@ private void ungetbyteCommon(byte[] ungetBytes, int ungetBegin, int ungetLen) { byteList.replace(start, ptr.pos - start, ungetBytes, ungetBegin, ungetLen); ptr.pos = start; + } finally { + unlock(ptr); } } @@ -1294,7 +1339,8 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg) StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { final Encoding enc = getEncoding(); final Encoding encStr = str.getEncoding(); if (enc != encStr && enc != EncodingUtils.ascii8bitEncoding(runtime) @@ -1326,6 +1372,8 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg) System.arraycopy(strByteList.getUnsafeBytes(), strByteList.getBegin(), ptrByteList.getUnsafeBytes(), ptrByteList.begin() + ptr.pos, len); } ptr.pos += len; + } finally { + unlock(ptr); } return len; @@ -1342,7 +1390,8 @@ public IRubyObject set_encoding(ThreadContext context, IRubyObject ext_enc) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { ptr.enc = enc; // in read-only mode, StringIO#set_encoding no longer sets the encoding @@ -1351,6 +1400,8 @@ public IRubyObject set_encoding(ThreadContext context, IRubyObject ext_enc) { string.modify(); string.setEncoding(enc); } + } finally { + unlock(ptr); } return this; @@ -1386,7 +1437,8 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { StringIOData ptr = this.ptr; - synchronized (ptr) { + lock(ptr); + try { final Encoding enc = getEncoding(); final ByteList string = ptr.string.getByteList(); final byte[] stringBytes = string.getUnsafeBytes(); @@ -1399,6 +1451,8 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { block.yield(context, runtime.newFixnum(c)); ptr.pos += n; } + } finally { + unlock(ptr); } } @@ -1627,4 +1681,12 @@ private void checkOpen() { throw getRuntime().newIOError(RubyIO.CLOSED_STREAM_MSG); } } + + private static void lock(StringIOData ptr) { + while (!LOCKED_UPDATER.compareAndSet(ptr, 0, 1)); // lock + } + + private static void unlock(StringIOData ptr) { + ptr.locked = 0; // unlock + } } From 06057c63f69f4f48a97d6bd83203b05b482895a9 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 21:55:25 -0500 Subject: [PATCH 4/8] Avoid repeat reads of fields --- ext/java/org/jruby/ext/stringio/StringIO.java | 128 ++++++++++-------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 48400d9..018bae0 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -117,7 +117,9 @@ public static RubyClass createStringIOClass(final Ruby runtime) { // mri: get_enc public Encoding getEncoding() { - return ptr.enc != null ? ptr.enc : ptr.string.getEncoding(); + StringIOData ptr = this.ptr; + Encoding enc = ptr.enc; + return enc != null ? enc : ptr.string.getEncoding(); } public void setEncoding(Encoding enc) { @@ -238,14 +240,14 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject other) { if (this == otherIO) return this; ptr = otherIO.ptr; - flags &= ~STRIO_READWRITE; - flags |= otherIO.flags & STRIO_READWRITE; + flags = flags & ~STRIO_READWRITE | otherIO.flags & STRIO_READWRITE; return this; } @JRubyMethod public IRubyObject binmode(ThreadContext context) { + StringIOData ptr = this.ptr; ptr.enc = EncodingUtils.ascii8bitEncoding(context.runtime); if (writable()) ptr.string.setEncoding(ptr.enc); @@ -307,8 +309,9 @@ public IRubyObject close_read(ThreadContext context) { if ( (ptr.flags & OpenFile.READABLE) == 0 ) { throw context.runtime.newIOError("not opened for reading"); } + int flags = this.flags; if ( ( flags & STRIO_READABLE ) != 0 ) { - flags &= ~STRIO_READABLE; + this.flags = flags & ~STRIO_READABLE; } return context.nil; } @@ -326,8 +329,9 @@ public IRubyObject close_write(ThreadContext context) { if ( (ptr.flags & OpenFile.WRITABLE) == 0 ) { throw context.runtime.newIOError("not opened for writing"); } + int flags = this.flags; if ( ( flags & STRIO_WRITABLE ) != 0 ) { - flags &= ~STRIO_WRITABLE; + this.flags = flags & ~STRIO_WRITABLE; } return context.nil; } @@ -440,11 +444,11 @@ public IRubyObject lines(ThreadContext context, IRubyObject[] args, Block block) @JRubyMethod(name = {"each_byte", "bytes"}) public IRubyObject each_byte(ThreadContext context, Block block) { - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_byte"); + Ruby runtime = context.runtime; - checkReadable(); + if (!block.isGiven()) return enumeratorize(runtime, this, "each_byte"); - Ruby runtime = context.runtime; + checkReadable(); StringIOData ptr = this.ptr; lock(ptr); @@ -503,11 +507,12 @@ public IRubyObject getc(ThreadContext context) { lock(ptr); try { int start = ptr.pos; - int total = 1 + StringSupport.bytesToFixBrokenTrailingCharacter(ptr.string.getByteList(), start + 1); + RubyString string = ptr.string; + int total = 1 + StringSupport.bytesToFixBrokenTrailingCharacter(string.getByteList(), start + 1); ptr.pos += total; - return context.runtime.newString(ptr.string.getByteList().makeShared(start, total)); + return context.runtime.newString(string.getByteList().makeShared(start, total)); } finally { unlock(ptr); } @@ -523,7 +528,7 @@ public IRubyObject getbyte(ThreadContext context) { StringIOData ptr = this.ptr; lock(ptr); try { - c = ptr.string.getByteList().get(this.ptr.pos++) & 0xFF; + c = ptr.string.getByteList().get(ptr.pos++) & 0xFF; } finally { unlock(ptr); } @@ -684,7 +689,8 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim final ByteList string = ptr.string.getByteList(); final byte[] stringBytes = string.getUnsafeBytes(); int begin = string.getBegin(); - int s = begin + ptr.pos; + int pos = ptr.pos; + int s = begin + pos; int e = begin + string.getRealSize(); int p; int w = 0; @@ -696,7 +702,7 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim if (chomp) { w = chompNewlineWidth(stringBytes, s, e); } - str = strioSubstr(runtime, ptr.pos, e - s - w, enc); + str = strioSubstr(runtime, pos, e - s - w, enc); } else if ((n = ((RubyString) rs).size()) == 0) { int paragraph_end = 0; p = s; @@ -731,7 +737,7 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim e = p + 1; w = (chomp ? ((p > s && stringBytes[p-1] == '\r')?1:0) + 1 : 0); } - str = strioSubstr(runtime, ptr.pos, e - s - w, enc); + str = strioSubstr(runtime, pos, e - s - w, enc); } else { if (n < e - s + (chomp ? 1 : 0)) { RubyString rsStr = (RubyString) rs; @@ -749,15 +755,15 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim } } else { int[] skip = new int[1 << CHAR_BIT]; - int pos; + int pos2; p = rsByteList.getBegin(); bm_init_skip(skip, rsBytes, p, n); - if ((pos = bm_search(rsBytes, p, n, stringBytes, s, e - s, skip)) >= 0) { - e = s + pos + n; + if ((pos2 = bm_search(rsBytes, p, n, stringBytes, s, e - s, skip)) >= 0) { + e = s + pos2 + n; } } } - str = strioSubstr(runtime, ptr.pos, e - s - w, enc); + str = strioSubstr(runtime, pos, e - s - w, enc); } ptr.pos = e - begin; ptr.lineno++; @@ -822,20 +828,22 @@ private void strioExtend(int pos, int len) { lock(ptr); try { - final int olen = ptr.string.size(); + RubyString string = ptr.string; + final int olen = string.size(); if (pos + len > olen) { - ptr.string.resize(pos + len); + string.resize(pos + len); if (pos > olen) { - ptr.string.modify19(); - ByteList ptrByteList = ptr.string.getByteList(); + string.modify19(); + ByteList ptrByteList = string.getByteList(); // zero the gap + int begin = ptrByteList.getBegin(); Arrays.fill(ptrByteList.getUnsafeBytes(), - ptrByteList.getBegin() + olen, - ptrByteList.getBegin() + pos, + begin + olen, + begin + pos, (byte) 0); } } else { - ptr.string.modify19(); + string.modify19(); } } finally { unlock(ptr); @@ -874,6 +882,7 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { boolean binary = false; StringIOData ptr = this.ptr; + int pos = ptr.pos; final RubyString string; lock(ptr); @@ -901,7 +910,7 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { } case 0: len = ptr.string.size(); - if (len <= ptr.pos) { + if (len <= pos) { Encoding enc = binary ? ASCIIEncoding.INSTANCE : getEncoding(); if (str.isNil()) { str = runtime.newString(); @@ -911,7 +920,7 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { ((RubyString) str).setEncoding(enc); return str; } else { - len -= ptr.pos; + len -= pos; } break; default: @@ -920,21 +929,22 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { if (str.isNil()) { Encoding enc = binary ? ASCIIEncoding.INSTANCE : getEncoding(); - string = strioSubstr(runtime, ptr.pos, len, enc); + string = strioSubstr(runtime, pos, len, enc); } else { string = (RubyString) str; - int rest = ptr.string.size() - ptr.pos; + RubyString myString = ptr.string; + int rest = myString.size() - pos; if (len > rest) len = rest; string.resize(len); ByteList strByteList = string.getByteList(); byte[] strBytes = strByteList.getUnsafeBytes(); - ByteList dataByteList = ptr.string.getByteList(); + ByteList dataByteList = myString.getByteList(); byte[] dataBytes = dataByteList.getUnsafeBytes(); - System.arraycopy(dataBytes, dataByteList.getBegin() + ptr.pos, strBytes, strByteList.getBegin(), len); + System.arraycopy(dataBytes, dataByteList.getBegin() + pos, strBytes, strByteList.getBegin(), len); if (binary) { string.setEncoding(ASCIIEncoding.INSTANCE); } else { - string.setEncoding(ptr.string.getEncoding()); + string.setEncoding(myString.getEncoding()); } } ptr.pos += string.size(); @@ -986,7 +996,8 @@ public IRubyObject pread(ThreadContext context, IRubyObject[] args) { lock(ptr); try { - if (offset >= ptr.string.size()) { + RubyString myString = ptr.string; + if (offset >= myString.size()) { throw context.runtime.newEOFError(); } @@ -995,12 +1006,12 @@ public IRubyObject pread(ThreadContext context, IRubyObject[] args) { } string = (RubyString) str; - int rest = ptr.string.size() - offset; + int rest = myString.size() - offset; if (len > rest) len = rest; string.resize(len); ByteList strByteList = string.getByteList(); byte[] strBytes = strByteList.getUnsafeBytes(); - ByteList dataByteList = ptr.string.getByteList(); + ByteList dataByteList = myString.getByteList(); byte[] dataBytes = dataByteList.getUnsafeBytes(); System.arraycopy(dataBytes, dataByteList.getBegin() + offset, strBytes, strByteList.getBegin(), len); string.setEncoding(ASCIIEncoding.INSTANCE); @@ -1224,10 +1235,11 @@ private void ungetbyteCommon(int c) { lock(ptr); try { - ptr.string.modify(); + RubyString string = ptr.string; + string.modify(); ptr.pos--; - ByteList bytes = ptr.string.getByteList(); + ByteList bytes = string.getByteList(); if (isEndOfString()) bytes.length(ptr.pos + 1); @@ -1256,19 +1268,21 @@ private void ungetbyteCommon(byte[] ungetBytes, int ungetBegin, int ungetLen) { lock(ptr); try { - ptr.string.modify(); + RubyString string = ptr.string; + string.modify(); - if (ungetLen > ptr.pos) { + int pos = ptr.pos; + if (ungetLen > pos) { start = 0; } else { - start = ptr.pos - ungetLen; + start = pos - ungetLen; } - ByteList byteList = ptr.string.getByteList(); + ByteList byteList = string.getByteList(); - if (isEndOfString()) byteList.length(Math.max(ptr.pos, ungetLen)); + if (isEndOfString()) byteList.length(Math.max(pos, ungetLen)); - byteList.replace(start, ptr.pos - start, ungetBytes, ungetBegin, ungetLen); + byteList.replace(start, pos - start, ungetBytes, ungetBegin, ungetLen); ptr.pos = start; } finally { @@ -1352,26 +1366,28 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg) len = str.size(); if (len == 0) return 0; checkModifiable(); - olen = ptr.string.size(); + RubyString myString = ptr.string; + olen = myString.size(); if ((ptr.flags & OpenFile.APPEND) != 0) { ptr.pos = olen; } - if (ptr.pos == olen) { + int pos = ptr.pos; + if (pos == olen) { if (enc == EncodingUtils.ascii8bitEncoding(runtime) || encStr == EncodingUtils.ascii8bitEncoding(runtime)) { - EncodingUtils.encStrBufCat(runtime, ptr.string, strByteList, enc); + EncodingUtils.encStrBufCat(runtime, myString, strByteList, enc); } else { try { - RubyString unused = (RubyString) CAT_WITH_CODE_RANGE.invokeExact(ptr.string, str); + RubyString unused = (RubyString) CAT_WITH_CODE_RANGE.invokeExact(myString, str); } catch (Throwable t) { throw new RuntimeException(t); } } } else { - strioExtend(ptr.pos, len); - ByteList ptrByteList = ptr.string.getByteList(); - System.arraycopy(strByteList.getUnsafeBytes(), strByteList.getBegin(), ptrByteList.getUnsafeBytes(), ptrByteList.begin() + ptr.pos, len); + strioExtend(pos, len); + ByteList ptrByteList = myString.getByteList(); + System.arraycopy(strByteList.getUnsafeBytes(), strByteList.getBegin(), ptrByteList.getUnsafeBytes(), ptrByteList.begin() + pos, len); } - ptr.pos += len; + ptr.pos = pos + len; } finally { unlock(ptr); } @@ -1440,16 +1456,18 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { lock(ptr); try { final Encoding enc = getEncoding(); - final ByteList string = ptr.string.getByteList(); + RubyString myString = ptr.string; + final ByteList string = myString.getByteList(); final byte[] stringBytes = string.getUnsafeBytes(); int begin = string.getBegin(); for (; ; ) { - if (ptr.pos >= ptr.string.size()) return this; + int pos = ptr.pos; + if (pos >= string.realSize()) return this; - int c = StringSupport.codePoint(runtime, enc, stringBytes, begin + ptr.pos, stringBytes.length); + int c = StringSupport.codePoint(runtime, enc, stringBytes, begin + pos, stringBytes.length); int n = StringSupport.codeLength(enc, c); block.yield(context, runtime.newFixnum(c)); - ptr.pos += n; + ptr.pos = pos + n; } } finally { unlock(ptr); From 05a48b5c61e463b0f5667442b8f1ec353679ac89 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 21:55:52 -0500 Subject: [PATCH 5/8] Remove codepoints, chars, bytes, and lines Removed in 2020 from CRuby (ruby/stringio@48fdd28e727ff2) after being deprecated since 2012. --- ext/java/org/jruby/ext/stringio/StringIO.java | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 018bae0..ae88b02 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -436,13 +436,7 @@ public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block bl } } - @JRubyMethod(name = "lines", optional = 2) - public IRubyObject lines(ThreadContext context, IRubyObject[] args, Block block) { - context.runtime.getWarnings().warn("StringIO#lines is deprecated; use #each_line instead"); - return block.isGiven() ? each(context, args, block) : enumeratorize(context.runtime, this, "each_line", args); - } - - @JRubyMethod(name = {"each_byte", "bytes"}) + @JRubyMethod(name = {"each_byte"}) public IRubyObject each_byte(ThreadContext context, Block block) { Ruby runtime = context.runtime; @@ -478,13 +472,6 @@ public IRubyObject each_char(final ThreadContext context, final Block block) { return this; } - @JRubyMethod - public IRubyObject chars(final ThreadContext context, final Block block) { - context.runtime.getWarnings().warn("StringIO#chars is deprecated; use #each_char instead"); - - return each_char(context, block); - } - @JRubyMethod(name = {"eof", "eof?"}) public IRubyObject eof(ThreadContext context) { checkReadable(); @@ -1474,16 +1461,6 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { } } - @JRubyMethod(name = "codepoints") - public IRubyObject codepoints(ThreadContext context, Block block) { - Ruby runtime = context.runtime; - runtime.getWarnings().warn("StringIO#codepoints is deprecated; use #each_codepoint"); - - if (!block.isGiven()) return enumeratorize(runtime, this, "each_codepoint"); - - return each_codepoint(context, block); - } - public static class GenericReadable { @JRubyMethod(name = "readchar") public static IRubyObject readchar(ThreadContext context, IRubyObject self) { From 237496c4c019365ada0d2ae060dbdcee07116079 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 21:57:48 -0500 Subject: [PATCH 6/8] Mark fcntl as not implemented --- ext/java/org/jruby/ext/stringio/StringIO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index ae88b02..11c8b09 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -259,7 +259,7 @@ public IRubyObject strio_self() { return this; } - @JRubyMethod(name = {"fcntl"}, rest = true) + @JRubyMethod(name = {"fcntl"}, rest = true, notImplemented = true) public IRubyObject strio_unimpl(ThreadContext context, IRubyObject[] args) { throw context.runtime.newNotImplementedError(""); } From 7e1309d12715b56cdb596a66f7d3450e03b68d6c Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 23:00:37 -0500 Subject: [PATCH 7/8] Split most remaining variable-arity methods The few I did not convert make calls back into JRuby that require argument arrays anyway, so no value in splitting them until JRuby has split paths downstream. --- ext/java/org/jruby/ext/stringio/StringIO.java | 432 ++++++++++++++---- 1 file changed, 342 insertions(+), 90 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 11c8b09..4a1ff3b 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -38,6 +38,7 @@ import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.ast.util.ArgsUtil; +import org.jruby.common.IRubyWarnings; import org.jruby.java.addons.IOJavaAddons; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; @@ -63,6 +64,8 @@ import static org.jruby.RubyEnumerator.enumeratorize; import static org.jruby.runtime.Visibility.PRIVATE; +import static org.jruby.util.RubyStringBuilder.str; +import static org.jruby.util.RubyStringBuilder.types; @JRubyClass(name="StringIO") @SuppressWarnings("serial") @@ -128,12 +131,95 @@ public void setEncoding(Encoding enc) { @JRubyMethod(name = "new", rest = true, meta = true) public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { - return RubyIO.newInstance(context, recv, args, block); + RubyClass klass = (RubyClass) recv; + + warnIfBlock(context, block, klass); + + return klass.newInstance(context, args, block); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, Block block) { + RubyClass klass = (RubyClass) recv; + + warnIfBlock(context, block, klass); + + return klass.newInstance(context, block); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) { + RubyClass klass = (RubyClass) recv; + + warnIfBlock(context, block, klass); + + return klass.newInstance(context, arg0, block); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) { + RubyClass klass = (RubyClass) recv; + + warnIfBlock(context, block, klass); + + return klass.newInstance(context, arg0, arg1, block); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + RubyClass klass = (RubyClass) recv; + + warnIfBlock(context, block, klass); + + return klass.newInstance(context, arg0, arg1, arg2, block); + } + + private static void warnIfBlock(ThreadContext context, Block block, RubyClass klass) { + if (block.isGiven()) { + Ruby runtime = context.runtime; + IRubyObject className = types(runtime, klass); + + runtime.getWarnings().warn(IRubyWarnings.ID.BLOCK_NOT_ACCEPTED, + str(runtime, className, "::new() does not take block; use ", className, "::open() instead")); + } } @JRubyMethod(meta = true, rest = true) public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { StringIO strio = (StringIO)((RubyClass)recv).newInstance(context, args, Block.NULL_BLOCK); + + return yieldOrReturn(context, block, strio); + } + + @JRubyMethod(meta = true) + public static IRubyObject open(ThreadContext context, IRubyObject recv, Block block) { + StringIO strio = (StringIO)((RubyClass)recv).newInstance(context, Block.NULL_BLOCK); + + return yieldOrReturn(context, block, strio); + } + + @JRubyMethod(meta = true) + public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) { + StringIO strio = (StringIO)((RubyClass)recv).newInstance(context, arg0, Block.NULL_BLOCK); + + return yieldOrReturn(context, block, strio); + } + + @JRubyMethod(meta = true) + public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) { + StringIO strio = (StringIO)((RubyClass)recv).newInstance(context, arg0, arg1, Block.NULL_BLOCK); + + return yieldOrReturn(context, block, strio); + } + + @JRubyMethod(meta = true) + public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + StringIO strio = (StringIO)((RubyClass)recv).newInstance(context, arg0, arg1, arg2, Block.NULL_BLOCK); + + return yieldOrReturn(context, block, strio); + } + + private static IRubyObject yieldOrReturn(ThreadContext context, Block block, StringIO strio) { IRubyObject val = strio; if (block.isGiven()) { @@ -144,6 +230,7 @@ public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObj strio.flags &= ~STRIO_READWRITE; } } + return val; } @@ -151,22 +238,44 @@ protected StringIO(Ruby runtime, RubyClass klass) { super(runtime, klass); } - @JRubyMethod(optional = 2, visibility = PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Arity.checkArgumentCount(context, args, 0, 2); + @JRubyMethod(visibility = PRIVATE) + public IRubyObject initialize(ThreadContext context) { + if (ptr == null) { + ptr = new StringIOData(); + } + + // does not dispatch quite right and is not really necessary for us + //Helpers.invokeSuper(context, this, metaClass, "initialize", IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + strioInit(context, 0, null, null); + return this; + } + + @JRubyMethod(visibility = PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { + if (ptr == null) { + ptr = new StringIOData(); + } + // does not dispatch quite right and is not really necessary for us + //Helpers.invokeSuper(context, this, metaClass, "initialize", IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + strioInit(context, 1, arg0, null); + return this; + } + + @JRubyMethod(visibility = PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { if (ptr == null) { ptr = new StringIOData(); } // does not dispatch quite right and is not really necessary for us //Helpers.invokeSuper(context, this, metaClass, "initialize", IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - strioInit(context, args); + strioInit(context, 2, arg0, arg1); return this; } // MRI: strio_init - private void strioInit(ThreadContext context, IRubyObject[] args) { + private void strioInit(ThreadContext context, int argc, IRubyObject arg0, IRubyObject arg1) { Ruby runtime = context.runtime; RubyString string; IRubyObject mode; @@ -175,10 +284,18 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { lock(ptr); try { - int argc = args.length; + IRubyObject maybeOptions = context.nil; + switch (argc) { + case 1: + maybeOptions = arg0; + break; + case 2: + maybeOptions = arg1; + break; + } Encoding encoding = null; - IRubyObject options = ArgsUtil.getOptionsArg(runtime, args); + IRubyObject options = ArgsUtil.getOptionsArg(runtime, maybeOptions); if (!options.isNil()) { argc--; IRubyObject encodingOpt = ArgsUtil.extractKeywordArg(context, "encoding", (RubyHash) options); @@ -189,18 +306,18 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { switch (argc) { case 2: - mode = args[1]; + mode = arg1; final boolean trunc; if (mode instanceof RubyFixnum) { int flags = RubyFixnum.fix2int(mode); ptr.flags = ModeFlags.getOpenFileFlagsFor(flags); trunc = (flags & ModeFlags.TRUNC) != 0; } else { - String m = args[1].convertToString().toString(); + String m = arg1.convertToString().toString(); ptr.flags = OpenFile.ioModestrFmode(runtime, m); trunc = m.length() > 0 && m.charAt(0) == 'w'; } - string = args[0].convertToString(); + string = arg0.convertToString(); if ((ptr.flags & OpenFile.WRITABLE) != 0 && string.isFrozen()) { throw runtime.newErrnoEACCESError("Permission denied"); } @@ -209,7 +326,7 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { } break; case 1: - string = args[0].convertToString(); + string = arg0.convertToString(); ptr.flags = string.isFrozen() ? OpenFile.READABLE : OpenFile.READWRITE; break; case 0: @@ -217,7 +334,8 @@ private void strioInit(ThreadContext context, IRubyObject[] args) { ptr.flags = OpenFile.READWRITE; break; default: - throw runtime.newArgumentError(args.length, 2); + // should not be possible + throw runtime.newArgumentError(3, 2); } ptr.string = string; @@ -858,32 +976,46 @@ public IRubyObject putc(ThreadContext context, IRubyObject ch) { public static final ByteList NEWLINE = ByteList.create("\n"); - @JRubyMethod(name = "read", optional = 2) + @JRubyMethod(name = "read") + public IRubyObject read(ThreadContext context) { + return readCommon(context, 0, null, null); + } + + @JRubyMethod(name = "read") + public IRubyObject read(ThreadContext context, IRubyObject arg0) { + return readCommon(context, 1, arg0, null); + } + + @JRubyMethod(name = "read") + public IRubyObject read(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + return readCommon(context, 2, arg0, arg1); + } + @SuppressWarnings("fallthrough") - public IRubyObject read(ThreadContext context, IRubyObject[] args) { + private IRubyObject readCommon(ThreadContext context, int argc, IRubyObject arg0, IRubyObject arg1) { checkReadable(); - final Ruby runtime = context.runtime; + Ruby runtime = context.runtime; + IRubyObject str = context.nil; - int len; boolean binary = false; - StringIOData ptr = this.ptr; int pos = ptr.pos; - final RubyString string; lock(ptr); try { - switch (args.length) { + int len; + final RubyString string; + switch (argc) { case 2: - str = args[1]; + str = arg1; if (!str.isNil()) { str = str.convertToString(); ((RubyString) str).modify(); } case 1: - if (!args[0].isNil()) { - len = RubyNumeric.fix2int(args[0]); + if (!arg0.isNil()) { + len = RubyNumeric.fix2int(arg0); if (len < 0) { throw runtime.newArgumentError("negative length " + len + " given"); @@ -911,7 +1043,7 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { } break; default: - throw runtime.newArgumentError(args.length, 0, 2); + throw runtime.newArgumentError(argc, 0, 2); } if (str.isNil()) { @@ -934,39 +1066,52 @@ public IRubyObject read(ThreadContext context, IRubyObject[] args) { string.setEncoding(myString.getEncoding()); } } + ptr.pos += string.size(); + + return string; } finally { unlock(ptr); } + } - return string; + @JRubyMethod(name = "pread") + public IRubyObject pread(ThreadContext context, IRubyObject arg0) { + return preadCommon(context, 1, arg0, null, null); } - @JRubyMethod(name = "pread", required = 2, optional = 1) - @SuppressWarnings("fallthrough") - public IRubyObject pread(ThreadContext context, IRubyObject[] args) { - checkReadable(); + @JRubyMethod(name = "pread") + public IRubyObject pread(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + return preadCommon(context, 2, arg0, arg1, null); + } - final Ruby runtime = context.runtime; - IRubyObject str = context.nil; - int len; - int offset; + @JRubyMethod(name = "pread") + public IRubyObject pread(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { + return preadCommon(context, 3, arg0, arg1, arg2); + } + @SuppressWarnings("fallthrough") + private RubyString preadCommon(ThreadContext context, int argc, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { + IRubyObject str = context.nil; StringIOData ptr = this.ptr; + Ruby runtime = context.runtime; + int offset; final RubyString string; + int len; + checkReadable(); - switch (args.length) { + switch (argc) { case 3: - str = args[2]; + str = arg2; if (!str.isNil()) { str = str.convertToString(); ((RubyString) str).modify(); } case 2: - len = RubyNumeric.fix2int(args[0]); - offset = RubyNumeric.fix2int(args[1]); - if (!args[0].isNil()) { - len = RubyNumeric.fix2int(args[0]); + len = RubyNumeric.fix2int(arg0); + offset = RubyNumeric.fix2int(arg1); + if (!arg0.isNil()) { + len = RubyNumeric.fix2int(arg0); if (len < 0) { throw runtime.newArgumentError("negative length " + len + " given"); @@ -978,7 +1123,7 @@ public IRubyObject pread(ThreadContext context, IRubyObject[] args) { } break; default: - throw runtime.newArgumentError(args.length, 0, 2); + throw runtime.newArgumentError(argc, 0, 2); } lock(ptr); @@ -1046,18 +1191,34 @@ public IRubyObject readlines(ThreadContext context, IRubyObject[] args) { } // MRI: strio_reopen - @JRubyMethod(name = "reopen", optional = 2) - public IRubyObject reopen(ThreadContext context, IRubyObject[] args) { - int argc = Arity.checkArgumentCount(context, args, 0, 2); + @JRubyMethod(name = "reopen") + public IRubyObject reopen(ThreadContext context) { + // reset the state + strioInit(context, 0, null, null); + return this; + } + // MRI: strio_reopen + @JRubyMethod(name = "reopen") + public IRubyObject reopen(ThreadContext context, IRubyObject arg0) { checkFrozen(); - if (argc == 1 && !(args[0] instanceof RubyString)) { - return initialize_copy(context, args[0]); + if (!(arg0 instanceof RubyString)) { + return initialize_copy(context, arg0); } // reset the state - strioInit(context, args); + strioInit(context, 1, arg0, null); + return this; + } + + // MRI: strio_reopen + @JRubyMethod(name = "reopen") + public IRubyObject reopen(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + checkFrozen(); + + // reset the state + strioInit(context, 2, arg0, arg1); return this; } @@ -1078,19 +1239,28 @@ public IRubyObject rewind(ThreadContext context) { return RubyFixnum.zero(context.runtime); } - @JRubyMethod(required = 1, optional = 1) - public IRubyObject seek(ThreadContext context, IRubyObject[] args) { - int argc = Arity.checkArgumentCount(context, args, 1, 2); + @JRubyMethod + public IRubyObject seek(ThreadContext context, IRubyObject arg0) { + return seekCommon(context, 1, arg0, null); + } - Ruby runtime = context.runtime; + @JRubyMethod + public IRubyObject seek(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + return seekCommon(context, 2, arg0, arg1); + } + private RubyFixnum seekCommon(ThreadContext context, int argc, IRubyObject arg0, IRubyObject arg1) { checkFrozen(); checkFinalized(); - int offset = RubyNumeric.num2int(args[0]); + Ruby runtime = context.runtime; + IRubyObject whence = context.nil; + int offset = RubyNumeric.num2int(arg0); - if (argc > 1 && !args[0].isNil()) whence = args[1]; + if (argc > 1 && !arg0.isNil()) { + whence = arg1; + } checkOpen(); @@ -1153,11 +1323,6 @@ public IRubyObject sync(ThreadContext context) { return context.tru; } - // only here for the fake-out class in org.jruby - public IRubyObject sysread(IRubyObject[] args) { - return GenericReadable.sysread(getRuntime().getCurrentContext(), this, args); - } - @JRubyMethod(name = "truncate", required = 1) public IRubyObject truncate(IRubyObject len) { checkWritable(); @@ -1302,6 +1467,25 @@ public IRubyObject write(ThreadContext context, IRubyObject arg) { return RubyFixnum.newFixnum(runtime, stringIOWrite(context, runtime, arg)); } + @JRubyMethod(name = "write") + public IRubyObject write(ThreadContext context, IRubyObject arg0, IRubyObject arg1) { + Ruby runtime = context.runtime; + long len = 0; + len += stringIOWrite(context, runtime, arg0); + len += stringIOWrite(context, runtime, arg1); + return RubyFixnum.newFixnum(runtime, len); + } + + @JRubyMethod(name = "write") + public IRubyObject write(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { + Ruby runtime = context.runtime; + long len = 0; + len += stringIOWrite(context, runtime, arg0); + len += stringIOWrite(context, runtime, arg1); + len += stringIOWrite(context, runtime, arg2); + return RubyFixnum.newFixnum(runtime, len); + } + @JRubyMethod(name = "write", required = 1, rest = true) public IRubyObject write(ThreadContext context, IRubyObject[] args) { Arity.checkArgumentCount(context, args, 1, -1); @@ -1480,18 +1664,45 @@ public static IRubyObject readbyte(ThreadContext context, IRubyObject self) { return b; } - @JRubyMethod(name = "readline", optional = 1, writes = FrameField.LASTLINE) - public static IRubyObject readline(ThreadContext context, IRubyObject self, IRubyObject[] args) { - IRubyObject line = self.callMethod(context, "gets", args); + @JRubyMethod(name = "readline", writes = FrameField.LASTLINE) + public static IRubyObject readline(ThreadContext context, IRubyObject self) { + IRubyObject line = self.callMethod(context, "gets"); if (line.isNil()) throw context.runtime.newEOFError(); return line; } - @JRubyMethod(name = {"sysread", "readpartial"}, optional = 2) - public static IRubyObject sysread(ThreadContext context, IRubyObject self, IRubyObject[] args) { - IRubyObject val = self.callMethod(context, "read", args); + @JRubyMethod(name = "readline", writes = FrameField.LASTLINE) + public static IRubyObject readline(ThreadContext context, IRubyObject self, IRubyObject arg0) { + IRubyObject line = self.callMethod(context, "gets", arg0); + + if (line.isNil()) throw context.runtime.newEOFError(); + + return line; + } + + @JRubyMethod(name = {"sysread", "readpartial"}) + public static IRubyObject sysread(ThreadContext context, IRubyObject self) { + IRubyObject val = self.callMethod(context, "read"); + + if (val.isNil()) throw context.runtime.newEOFError(); + + return val; + } + + @JRubyMethod(name = {"sysread", "readpartial"}) + public static IRubyObject sysread(ThreadContext context, IRubyObject self, IRubyObject arg0) { + IRubyObject val = Helpers.invoke(context, self, "read", arg0); + + if (val.isNil()) throw context.runtime.newEOFError(); + + return val; + } + + @JRubyMethod(name = {"sysread", "readpartial"}) + public static IRubyObject sysread(ThreadContext context, IRubyObject self, IRubyObject arg0, IRubyObject arg1) { + IRubyObject val = Helpers.invoke(context, self, "read", arg0, arg1); if (val.isNil()) throw context.runtime.newEOFError(); @@ -1522,7 +1733,7 @@ public static IRubyObject read_nonblock(ThreadContext context, IRubyObject self, } public static class GenericWritable { - @JRubyMethod(name = "<<", required = 1) + @JRubyMethod(name = "<<") public static IRubyObject append(ThreadContext context, IRubyObject self, IRubyObject arg) { // Claims conversion is done via 'to_s' in docs. self.callMethod(context, "write", arg); @@ -1543,7 +1754,7 @@ public static IRubyObject printf(ThreadContext context, IRubyObject self, IRubyO @JRubyMethod(name = "puts", rest = true) public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject[] args) { - // TODO: This should defer to RubyIO logic, but we don't have puts right there for 1.9 + // TODO: This should defer to RubyIO logic? Ruby runtime = context.runtime; if (args.length == 0) { RubyIO.write(context, maybeIO, RubyString.newStringShared(runtime, NEWLINE)); @@ -1551,36 +1762,77 @@ public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRuby } for (int i = 0; i < args.length; i++) { - RubyString line = null; - - if (!args[i].isNil()) { - IRubyObject tmp = args[i].checkArrayType(); - if (!tmp.isNil()) { - @SuppressWarnings("unchecked") - RubyArray arr = (RubyArray) tmp; - if (runtime.isInspecting(arr)) { - line = runtime.newString("[...]"); - } else { - inspectPuts(context, maybeIO, arr); - continue; - } + putsArg(context, maybeIO, args[i], runtime); + } + + return context.nil; + } + + @JRubyMethod(name = "puts") + public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO) { + // TODO: This should defer to RubyIO logic? + RubyIO.write(context, maybeIO, RubyString.newStringShared(context.runtime, NEWLINE)); + return context.nil; + } + + @JRubyMethod(name = "puts") + public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0) { + // TODO: This should defer to RubyIO logic? + putsArg(context, maybeIO, arg0, context.runtime); + + return context.nil; + } + + @JRubyMethod(name = "puts") + public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0, IRubyObject arg1) { + // TODO: This should defer to RubyIO logic? + Ruby runtime = context.runtime; + + putsArg(context, maybeIO, arg0, runtime); + putsArg(context, maybeIO, arg1, runtime); + + return context.nil; + } + + @JRubyMethod(name = "puts") + public static IRubyObject puts(ThreadContext context, IRubyObject maybeIO, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { + // TODO: This should defer to RubyIO logic? + Ruby runtime = context.runtime; + + putsArg(context, maybeIO, arg0, runtime); + putsArg(context, maybeIO, arg1, runtime); + putsArg(context, maybeIO, arg2, runtime); + + return context.nil; + } + + private static void putsArg(ThreadContext context, IRubyObject maybeIO, IRubyObject arg, Ruby runtime) { + RubyString line = null; + if (!arg.isNil()) { + IRubyObject tmp = arg.checkArrayType(); + if (!tmp.isNil()) { + @SuppressWarnings("unchecked") + RubyArray arr = (RubyArray) tmp; + if (runtime.isInspecting(arr)) { + line = runtime.newString("[...]"); } else { - if (args[i] instanceof RubyString) { - line = (RubyString) args[i]; - } else { - line = args[i].asString(); - } + inspectPuts(context, maybeIO, arr); + return; + } + } else { + if (arg instanceof RubyString) { + line = (RubyString) arg; + } else { + line = arg.asString(); } } + } - if (line != null) RubyIO.write(context, maybeIO, line); + if (line != null) RubyIO.write(context, maybeIO, line); - if (line == null || !line.getByteList().endsWith(NEWLINE)) { - RubyIO.write(context, maybeIO, RubyString.newStringShared(runtime, NEWLINE)); - } + if (line == null || !line.getByteList().endsWith(NEWLINE)) { + RubyIO.write(context, maybeIO, RubyString.newStringShared(runtime, NEWLINE)); } - - return runtime.getNil(); } private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeIO, RubyArray array) { @@ -1594,7 +1846,7 @@ private static IRubyObject inspectPuts(ThreadContext context, IRubyObject maybeI } } - @JRubyMethod(name = "syswrite", required = 1) + @JRubyMethod(name = "syswrite") public static IRubyObject syswrite(ThreadContext context, IRubyObject self, IRubyObject arg) { return RubyIO.write(context, self, arg); } From 32194b3551de58bb87e67a2da794ba41fa7b2312 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 12 Mar 2024 23:40:55 -0500 Subject: [PATCH 8/8] Make spinlock reentrant --- ext/java/org/jruby/ext/stringio/StringIO.java | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 4a1ff3b..2da72d7 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -60,7 +60,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import static org.jruby.RubyEnumerator.enumeratorize; import static org.jruby.runtime.Visibility.PRIVATE; @@ -80,7 +80,7 @@ static class StringIOData { int pos; int lineno; int flags; - volatile int locked; + volatile Object owner; } StringIOData ptr; @@ -91,7 +91,7 @@ static class StringIOData { private static final int STRIO_WRITABLE = ObjectFlags.registry.newFlag(StringIO.class); private static final int STRIO_READWRITE = (STRIO_READABLE | STRIO_WRITABLE); - private static final AtomicIntegerFieldUpdater LOCKED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(StringIOData.class, "locked"); + private static final AtomicReferenceFieldUpdater LOCKED_UPDATER = AtomicReferenceFieldUpdater.newUpdater(StringIOData.class, Object.class, "owner"); public static RubyClass createStringIOClass(final Ruby runtime) { RubyClass stringIOClass = runtime.defineClass( @@ -282,7 +282,7 @@ private void strioInit(ThreadContext context, int argc, IRubyObject arg0, IRubyO StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { IRubyObject maybeOptions = context.nil; switch (argc) { @@ -345,7 +345,7 @@ private void strioInit(ThreadContext context, int argc, IRubyObject arg0, IRubyO // funky way of shifting readwrite flags into object flags flags |= (ptr.flags & OpenFile.READWRITE) * (STRIO_READABLE / OpenFile.READABLE); } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -563,7 +563,7 @@ public IRubyObject each_byte(ThreadContext context, Block block) { checkReadable(); StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { ByteList bytes = ptr.string.getByteList(); @@ -573,7 +573,7 @@ public IRubyObject each_byte(ThreadContext context, Block block) { block.yield(context, runtime.newFixnum(bytes.get(ptr.pos++) & 0xFF)); } } finally { - unlock(ptr); + if (locked) unlock(ptr); } return this; @@ -609,7 +609,7 @@ public IRubyObject getc(ThreadContext context) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { int start = ptr.pos; RubyString string = ptr.string; @@ -619,7 +619,7 @@ public IRubyObject getc(ThreadContext context) { return context.runtime.newString(string.getByteList().makeShared(start, total)); } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -631,11 +631,11 @@ public IRubyObject getbyte(ThreadContext context) { int c; StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { c = ptr.string.getByteList().get(ptr.pos++) & 0xFF; } finally { - unlock(ptr); + if (locked) unlock(ptr); } return context.runtime.newFixnum(c); @@ -789,7 +789,7 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim StringIOData ptr = this.ptr; Encoding enc = getEncoding(); - lock(ptr); + boolean locked = lock(context, ptr); try { final ByteList string = ptr.string.getByteList(); final byte[] stringBytes = string.getUnsafeBytes(); @@ -873,7 +873,7 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim ptr.pos = e - begin; ptr.lineno++; } finally { - unlock(ptr); + if (locked) unlock(ptr); } return str; @@ -928,10 +928,10 @@ public IRubyObject set_pos(IRubyObject arg) { return arg; } - private void strioExtend(int pos, int len) { + private void strioExtend(ThreadContext context, int pos, int len) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { RubyString string = ptr.string; final int olen = string.size(); @@ -951,7 +951,7 @@ private void strioExtend(int pos, int len) { string.modify19(); } } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -1002,7 +1002,7 @@ private IRubyObject readCommon(ThreadContext context, int argc, IRubyObject arg0 StringIOData ptr = this.ptr; int pos = ptr.pos; - lock(ptr); + boolean locked = lock(context, ptr); try { int len; final RubyString string; @@ -1071,7 +1071,7 @@ private IRubyObject readCommon(ThreadContext context, int argc, IRubyObject arg0 return string; } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -1126,7 +1126,7 @@ private RubyString preadCommon(ThreadContext context, int argc, IRubyObject arg0 throw runtime.newArgumentError(argc, 0, 2); } - lock(ptr); + boolean locked = lock(context, ptr); try { RubyString myString = ptr.string; if (offset >= myString.size()) { @@ -1148,7 +1148,7 @@ private RubyString preadCommon(ThreadContext context, int argc, IRubyObject arg0 System.arraycopy(dataBytes, dataByteList.getBegin() + offset, strBytes, strByteList.getBegin(), len); string.setEncoding(ASCIIEncoding.INSTANCE); } finally { - unlock(ptr); + if (locked) unlock(ptr); } return string; @@ -1228,12 +1228,12 @@ public IRubyObject rewind(ThreadContext context) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { ptr.pos = 0; ptr.lineno = 0; } finally { - unlock(ptr); + if (locked) unlock(ptr); } return RubyFixnum.zero(context.runtime); @@ -1266,7 +1266,7 @@ private RubyFixnum seekCommon(ThreadContext context, int argc, IRubyObject arg0, StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { switch (whence.isNil() ? 0 : RubyNumeric.num2int(whence)) { case 0: @@ -1285,18 +1285,18 @@ private RubyFixnum seekCommon(ThreadContext context, int argc, IRubyObject arg0, ptr.pos = offset; } finally { - unlock(ptr); + if (locked) unlock(ptr); } return RubyFixnum.zero(runtime); } @JRubyMethod(name = "string=", required = 1) - public IRubyObject set_string(IRubyObject arg) { + public IRubyObject set_string(ThreadContext context, IRubyObject arg) { checkFrozen(); StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { ptr.flags &= ~OpenFile.READWRITE; RubyString str = arg.convertToString(); @@ -1305,7 +1305,7 @@ public IRubyObject set_string(IRubyObject arg) { ptr.lineno = 0; return ptr.string = str; } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -1324,18 +1324,18 @@ public IRubyObject sync(ThreadContext context) { } @JRubyMethod(name = "truncate", required = 1) - public IRubyObject truncate(IRubyObject len) { + public IRubyObject truncate(ThreadContext context, IRubyObject len) { checkWritable(); int l = RubyFixnum.fix2int(len); StringIOData ptr = this.ptr; RubyString string = ptr.string; - lock(ptr); + boolean locked = lock(context, ptr); try { int plen = string.size(); if (l < 0) { - throw getRuntime().newErrnoEINVALError("negative legnth"); + throw context.runtime.newErrnoEINVALError("negative legnth"); } string.resize(l); ByteList buf = string.getByteList(); @@ -1344,7 +1344,7 @@ public IRubyObject truncate(IRubyObject len) { Arrays.fill(buf.getUnsafeBytes(), buf.getBegin() + plen, buf.getBegin() + l, (byte) 0); } } finally { - unlock(ptr); + if (locked) unlock(ptr); } return len; @@ -1366,7 +1366,7 @@ public IRubyObject ungetc(ThreadContext context, IRubyObject arg) { len = enc.codeToMbcLength(cc); if (len <= 0) EncodingUtils.encUintChr(context, cc, enc); enc.codeToMbc(cc, buf, 0); - ungetbyteCommon(buf, 0, len); + ungetbyteCommon(context, buf, 0, len); return context.nil; } else { arg = arg.convertToString(); @@ -1377,15 +1377,15 @@ public IRubyObject ungetc(ThreadContext context, IRubyObject arg) { argStr = EncodingUtils.strConvEnc(context, argStr, enc2, enc); } ByteList argBytes = argStr.getByteList(); - ungetbyteCommon(argBytes.unsafeBytes(), argBytes.begin(), argBytes.realSize()); + ungetbyteCommon(context, argBytes.unsafeBytes(), argBytes.begin(), argBytes.realSize()); return context.nil; } } - private void ungetbyteCommon(int c) { + private void ungetbyteCommon(ThreadContext context, int c) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { RubyString string = ptr.string; string.modify(); @@ -1402,23 +1402,23 @@ private void ungetbyteCommon(int c) { bytes.set(ptr.pos, c); } } finally { - unlock(ptr); + if (locked) unlock(ptr); } } - private void ungetbyteCommon(RubyString ungetBytes) { + private void ungetbyteCommon(ThreadContext context, RubyString ungetBytes) { ByteList ungetByteList = ungetBytes.getByteList(); - ungetbyteCommon(ungetByteList.unsafeBytes(), ungetByteList.begin(), ungetByteList.realSize()); + ungetbyteCommon(context, ungetByteList.unsafeBytes(), ungetByteList.begin(), ungetByteList.realSize()); } - private void ungetbyteCommon(byte[] ungetBytes, int ungetBegin, int ungetLen) { + private void ungetbyteCommon(ThreadContext context, byte[] ungetBytes, int ungetBegin, int ungetLen) { final int start; // = ptr.pos; if (ungetLen == 0) return; StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { RubyString string = ptr.string; string.modify(); @@ -1438,7 +1438,7 @@ private void ungetbyteCommon(byte[] ungetBytes, int ungetBegin, int ungetLen) { ptr.pos = start; } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -1452,9 +1452,9 @@ public IRubyObject ungetbyte(ThreadContext context, IRubyObject arg) { checkModifiable(); if (arg instanceof RubyInteger) { - ungetbyteCommon(((RubyInteger) ((RubyInteger) arg).op_mod(context, 256)).getIntValue()); + ungetbyteCommon(context, ((RubyInteger) ((RubyInteger) arg).op_mod(context, 256)).getIntValue()); } else { - ungetbyteCommon(arg.convertToString()); + ungetbyteCommon(context, arg.convertToString()); } return context.nil; @@ -1524,7 +1524,7 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg) StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { final Encoding enc = getEncoding(); final Encoding encStr = str.getEncoding(); @@ -1554,13 +1554,13 @@ private long stringIOWrite(ThreadContext context, Ruby runtime, IRubyObject arg) } } } else { - strioExtend(pos, len); + strioExtend(context, pos, len); ByteList ptrByteList = myString.getByteList(); System.arraycopy(strByteList.getUnsafeBytes(), strByteList.getBegin(), ptrByteList.getUnsafeBytes(), ptrByteList.begin() + pos, len); } ptr.pos = pos + len; } finally { - unlock(ptr); + if (locked) unlock(ptr); } return len; @@ -1577,7 +1577,7 @@ public IRubyObject set_encoding(ThreadContext context, IRubyObject ext_enc) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { ptr.enc = enc; @@ -1588,7 +1588,7 @@ public IRubyObject set_encoding(ThreadContext context, IRubyObject ext_enc) { string.setEncoding(enc); } } finally { - unlock(ptr); + if (locked) unlock(ptr); } return this; @@ -1624,7 +1624,7 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { StringIOData ptr = this.ptr; - lock(ptr); + boolean locked = lock(context, ptr); try { final Encoding enc = getEncoding(); RubyString myString = ptr.string; @@ -1641,7 +1641,7 @@ public IRubyObject each_codepoint(ThreadContext context, Block block) { ptr.pos = pos + n; } } finally { - unlock(ptr); + if (locked) unlock(ptr); } } @@ -1929,11 +1929,13 @@ private void checkOpen() { } } - private static void lock(StringIOData ptr) { - while (!LOCKED_UPDATER.compareAndSet(ptr, 0, 1)); // lock + private static boolean lock(ThreadContext context, StringIOData ptr) { + if (ptr.owner == context) return false; + while (!LOCKED_UPDATER.compareAndSet(ptr, null, context)); // lock + return true; } private static void unlock(StringIOData ptr) { - ptr.locked = 0; // unlock + ptr.owner = null; // unlock } }