diff options
-rwxr-xr-x[-rw-r--r--] | bootstrap.sh | 2 | ||||
-rw-r--r-- | compiler/ast.nim | 1 | ||||
-rw-r--r-- | compiler/jsgen.nim | 24 | ||||
-rw-r--r-- | compiler/semtypes.nim | 17 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 25 | ||||
-rw-r--r-- | compiler/vmdeps.nim | 130 | ||||
-rw-r--r-- | doc/nep1.rst | 6 | ||||
-rw-r--r-- | lib/pure/ioselectors.nim | 1764 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 28 | ||||
-rw-r--r-- | lib/pure/terminal.nim | 26 | ||||
-rw-r--r-- | lib/windows/winlean.nim | 21 | ||||
-rw-r--r-- | tests/async/tioselectors.nim | 407 | ||||
-rw-r--r-- | tests/async/tioselectors.nim.cfg | 1 | ||||
-rw-r--r-- | tests/generics/t88.nim | 25 | ||||
-rw-r--r-- | tools/website.tmpl | 1 | ||||
-rw-r--r-- | web/assets/bountysource/xored.png | bin | 0 -> 6258 bytes | |||
-rw-r--r-- | web/assets/style.css | 6 | ||||
-rw-r--r-- | web/news.rst | 3 | ||||
-rw-r--r-- | web/news/2016_06_23_launching_the_2016_nim_community_survey.rst | 29 | ||||
-rw-r--r-- | web/sponsors.csv | 17 | ||||
-rw-r--r-- | web/ticker.html | 10 |
21 files changed, 2448 insertions, 95 deletions
diff --git a/bootstrap.sh b/bootstrap.sh index 7f19c2440..dd551b52d 100644..100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -18,5 +18,3 @@ set +x echo echo 'Install Nim using "./install.sh <dir>" or "sudo ./install.sh <dir>".' - -exit 0 diff --git a/compiler/ast.nim b/compiler/ast.nim index 73c99c0b1..c3bb7cd62 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -590,6 +590,7 @@ type mNewString, mNewStringOfCap, mParseBiggestFloat, mReset, mArray, mOpenArray, mRange, mSet, mSeq, mVarargs, + mRef, mPtr, mVar, mDistinct, mVoid, mTuple, mOrdinal, mInt, mInt8, mInt16, mInt32, mInt64, mUInt, mUInt8, mUInt16, mUInt32, mUInt64, diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 73b627f91..8a631bb66 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1690,7 +1690,29 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mMinusSet: binaryExpr(p, n, r, "SetMinus", "SetMinus($1, $2)") of mIncl: binaryExpr(p, n, r, "", "$1[$2] = true") of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]" | "unset $1[$2]") - of mInSet: binaryExpr(p, n, r, "", "($1[$2] != undefined)" | "isset($1[$2])") + of mInSet: + if p.target == targetJS: + binaryExpr(p, n, r, "", "($1[$2] != undefined)") + else: + let s = n.sons[1] + if s.kind == nkCurly: + var a, b, x: TCompRes + gen(p, n.sons[2], x) + r.res = rope("(") + r.kind = resExpr + for i in countup(0, sonsLen(s) - 1): + if i > 0: add(r.res, " || ") + var it = s.sons[i] + if it.kind == nkRange: + gen(p, it.sons[0], a) + gen(p, it.sons[1], b) + addf(r.res, "($1 >= $2 && $1 <= $3)", [x.res, a.res, b.res,]) + else: + gen(p, it, a) + addf(r.res, "($1 == $2)", [x.res, a.res]) + add(r.res, ")") + else: + binaryExpr(p, n, r, "", "isset($1[$2])") of mNewSeq: genNewSeq(p, n) of mOf: genOf(p, n, r) of mReset: genReset(p, n) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 13b283fe5..e9a2be1bd 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -130,7 +130,7 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = if n.len < 1: result = newConstraint(c, kind) else: - let isCall = ord(n.kind in nkCallKinds) + let isCall = ord(n.kind in nkCallKinds+{nkBracketExpr}) let n = if n[0].kind == nkBracket: n[0] else: n checkMinSonsLen(n, 1) var base = semTypeNode(c, n.lastSon, nil) @@ -370,7 +370,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType = result.n = newNodeI(nkRecList, n.info) var check = initIntSet() var counter = 0 - for i in countup(0, sonsLen(n) - 1): + for i in countup(ord(n.kind == nkBracketExpr), sonsLen(n) - 1): var a = n.sons[i] if (a.kind != nkIdentDefs): illFormedAst(a) checkMinSonsLen(a, 3) @@ -1248,6 +1248,19 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = copyType(result, getCurrOwner(), false) for i in countup(1, n.len - 1): result.rawAddSon(semTypeNode(c, n.sons[i], nil)) + of mDistinct: + result = newOrPrevType(tyDistinct, prev, c) + addSonSkipIntLit(result, semTypeNode(c, n[1], nil)) + of mVar: + result = newOrPrevType(tyVar, prev, c) + var base = semTypeNode(c, n.sons[1], nil) + if base.kind == tyVar: + localError(n.info, errVarVarTypeNotAllowed) + base = base.sons[0] + addSonSkipIntLit(result, base) + of mRef: result = semAnyRef(c, n, tyRef, prev) + of mPtr: result = semAnyRef(c, n, tyPtr, prev) + of mTuple: result = semTuple(c, n, prev) else: result = semGeneric(c, n, s, prev) of nkDotExpr: var typeExpr = semExpr(c, n) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index df27e3c1d..70fbe7358 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -363,6 +363,25 @@ proc isObjectSubtype(a, f: PType): int = if t != nil: result = depth +proc skipToGenericBody(t: PType): PType = + var r = t + while r != nil: + if r.kind in {tyGenericInst, tyGenericInvocation}: + return r.sons[0] + r = if r.len > 0: r.lastSon else: nil + +proc isGenericSubtype(a, f: PType, d: var int): bool = + assert f.kind in {tyGenericInst, tyGenericInvocation, tyGenericBody} + var t = if a.kind == tyGenericBody: a else: a.skipToGenericBody + var r = if f.kind == tyGenericBody: f else: f.skipToGenericBody + var depth = 0 + while t != nil and not sameObjectTypes(r, t): + t = t.skipToGenericBody + inc depth + if t != nil: + d = depth + result = true + proc minRel(a, b: TTypeRelation): TTypeRelation = if a <= b: result = a else: result = b @@ -647,7 +666,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = template bindingRet(res) = if doBind: let bound = aOrig.skipTypes({tyRange}).skipIntLit - if doBind: put(c.bindings, f, bound) + put(c.bindings, f, bound) return res template considerPreviousT(body: stmt) {.immediate.} = @@ -945,17 +964,19 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyGenericInvocation: var x = a.skipGenericAlias + var depth = 0 if x.kind == tyGenericInvocation or f.sons[0].kind != tyGenericBody: #InternalError("typeRel: tyGenericInvocation -> tyGenericInvocation") # simply no match for now: discard elif x.kind == tyGenericInst and - (f.sons[0] == x.sons[0]) and + ((f.sons[0] == x.sons[0]) or isGenericSubtype(x, f, depth)) and (sonsLen(x) - 1 == sonsLen(f)): for i in countup(1, sonsLen(f) - 1): if x.sons[i].kind == tyGenericParam: internalError("wrong instantiated type!") elif typeRel(c, f.sons[i], x.sons[i]) <= isSubtype: return + c.inheritancePenalty += depth result = isGeneric else: let genericBody = f.sons[0] diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index 678a765f4..00b13e6b6 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -63,8 +63,9 @@ proc opSlurp*(file: string, info: TLineInfo, module: PSym): string = localError(info, errCannotOpenFile, file) result = "" -proc atomicTypeX(name: string; t: PType; info: TLineInfo): PNode = +proc atomicTypeX(name: string; m: TMagic; t: PType; info: TLineInfo): PNode = let sym = newSym(skType, getIdent(name), t.owner, info) + sym.magic = m sym.typ = t result = newSymNode(sym) result.typ = t @@ -72,13 +73,13 @@ proc atomicTypeX(name: string; t: PType; info: TLineInfo): PNode = proc mapTypeToAstX(t: PType; info: TLineInfo; inst=false; allowRecursionX=false): PNode -proc mapTypeToBracketX(name: string; t: PType; info: TLineInfo; +proc mapTypeToBracketX(name: string; m: TMagic; t: PType; info: TLineInfo; inst=false): PNode = result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicTypeX(name, t, info) + result.add atomicTypeX(name, m, t, info) for i in 0 .. < t.len: if t.sons[i] == nil: - let void = atomicTypeX("void", t, info) + let void = atomicTypeX("void", mVoid, t, info) void.typ = newType(tyEmpty, t.owner) result.add void else: @@ -87,14 +88,14 @@ proc mapTypeToBracketX(name: string; t: PType; info: TLineInfo; proc mapTypeToAstX(t: PType; info: TLineInfo; inst=false; allowRecursionX=false): PNode = var allowRecursion = allowRecursionX - template atomicType(name): expr = atomicTypeX(name, t, info) + template atomicType(name, m): expr = atomicTypeX(name, m, t, info) template mapTypeToAst(t,info): expr = mapTypeToAstX(t, info, inst) template mapTypeToAstR(t,info): expr = mapTypeToAstX(t, info, inst, true) template mapTypeToAst(t,i,info): expr = if i<t.len and t.sons[i]!=nil: mapTypeToAstX(t.sons[i], info, inst) else: ast.emptyNode - template mapTypeToBracket(name,t,info): expr = - mapTypeToBracketX(name, t, info, inst) + template mapTypeToBracket(name, m, t, info): expr = + mapTypeToBracketX(name, m, t, info, inst) template newNodeX(kind):expr = newNodeIT(kind, if t.n.isNil: info else: t.n.info, t) template newIdent(s):expr = @@ -114,19 +115,19 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; if allowRecursion: # getTypeImpl behavior: turn off recursion allowRecursion = false else: # getTypeInst behavior: return symbol - return atomicType(t.sym.name.s) + return atomicType(t.sym.name.s, t.sym.magic) case t.kind - of tyNone: result = atomicType("none") - of tyBool: result = atomicType("bool") - of tyChar: result = atomicType("char") - of tyNil: result = atomicType("nil") - of tyExpr: result = atomicType("expr") - of tyStmt: result = atomicType("stmt") - of tyEmpty: result = atomicType"void" + of tyNone: result = atomicType("none", mNone) + of tyBool: result = atomicType("bool", mBool) + of tyChar: result = atomicType("char", mChar) + of tyNil: result = atomicType("nil", mNil) + of tyExpr: result = atomicType("expr", mExpr) + of tyStmt: result = atomicType("stmt", mStmt) + of tyEmpty: result = atomicType("void", mVoid) of tyArrayConstr, tyArray: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicType("array") + result.add atomicType("array", mArray) if inst and t.sons[0].kind == tyRange: var rng = newNodeX(nkInfix) rng.add newIdentNode(getIdent(".."), info) @@ -139,10 +140,10 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyTypeDesc: if t.base != nil: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicType("typeDesc") + result.add atomicType("typeDesc", mTypeDesc) result.add mapTypeToAst(t.base, info) else: - result = atomicType"typeDesc" + result = atomicType("typeDesc", mTypeDesc) of tyGenericInvocation: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) for i in 0 .. < t.len: @@ -166,10 +167,11 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add mapTypeToAst(t.sons[0], info) else: if allowRecursion or t.sym==nil: - result = mapTypeToBracket("distinct", t, info) + result = mapTypeToBracket("distinct", mDistinct, t, info) else: - result = atomicType(t.sym.name.s) - of tyGenericParam, tyForward: result = atomicType(t.sym.name.s) + result = atomicType(t.sym.name.s, t.sym.magic) + of tyGenericParam, tyForward: + result = atomicType(t.sym.name.s, t.sym.magic) of tyObject: if inst: result = newNodeX(nkObjectTy) @@ -196,7 +198,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add mapTypeToAst(t.sons[0], info) result.add copyTree(t.n) else: - result = atomicType(t.sym.name.s) + result = atomicType(t.sym.name.s, t.sym.magic) of tyEnum: result = newNodeIT(nkEnumTy, if t.n.isNil: info else: t.n.info, t) result.add copyTree(t.n) @@ -206,22 +208,22 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; for s in t.n.sons: result.add newIdentDefs(s) else: - result = mapTypeToBracket("tuple", t, info) - of tySet: result = mapTypeToBracket("set", t, info) + result = mapTypeToBracket("tuple", mTuple, t, info) + of tySet: result = mapTypeToBracket("set", mSet, t, info) of tyPtr: if inst: result = newNodeX(nkPtrTy) result.add mapTypeToAst(t.sons[0], info) else: - result = mapTypeToBracket("ptr", t, info) + result = mapTypeToBracket("ptr", mPtr, t, info) of tyRef: if inst: result = newNodeX(nkRefTy) result.add mapTypeToAst(t.sons[0], info) else: - result = mapTypeToBracket("ref", t, info) - of tyVar: result = mapTypeToBracket("var", t, info) - of tySequence: result = mapTypeToBracket("seq", t, info) + result = mapTypeToBracket("ref", mRef, t, info) + of tyVar: result = mapTypeToBracket("var", mVar, t, info) + of tySequence: result = mapTypeToBracket("seq", mSeq, t, info) of tyProc: if inst: result = newNodeX(nkProcTy) @@ -235,52 +237,54 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add fp result.add ast.emptyNode # pragmas aren't reconstructed yet else: - result = mapTypeToBracket("proc", t, info) - of tyOpenArray: result = mapTypeToBracket("openArray", t, info) + result = mapTypeToBracket("proc", mNone, t, info) + of tyOpenArray: result = mapTypeToBracket("openArray", mOpenArray, t, info) of tyRange: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicType("range") + result.add atomicType("range", mRange) result.add t.n.sons[0].copyTree result.add t.n.sons[1].copyTree - of tyPointer: result = atomicType"pointer" - of tyString: result = atomicType"string" - of tyCString: result = atomicType"cstring" - of tyInt: result = atomicType"int" - of tyInt8: result = atomicType"int8" - of tyInt16: result = atomicType"int16" - of tyInt32: result = atomicType"int32" - of tyInt64: result = atomicType"int64" - of tyFloat: result = atomicType"float" - of tyFloat32: result = atomicType"float32" - of tyFloat64: result = atomicType"float64" - of tyFloat128: result = atomicType"float128" - of tyUInt: result = atomicType"uint" - of tyUInt8: result = atomicType"uint8" - of tyUInt16: result = atomicType"uint16" - of tyUInt32: result = atomicType"uint32" - of tyUInt64: result = atomicType"uint64" - of tyBigNum: result = atomicType"bignum" - of tyConst: result = mapTypeToBracket("const", t, info) - of tyMutable: result = mapTypeToBracket("mutable", t, info) - of tyVarargs: result = mapTypeToBracket("varargs", t, info) - of tyIter: result = mapTypeToBracket("iter", t, info) - of tyProxy: result = atomicType"error" - of tyBuiltInTypeClass: result = mapTypeToBracket("builtinTypeClass", t, info) + of tyPointer: result = atomicType("pointer", mPointer) + of tyString: result = atomicType("string", mString) + of tyCString: result = atomicType("cstring", mCString) + of tyInt: result = atomicType("int", mInt) + of tyInt8: result = atomicType("int8", mInt8) + of tyInt16: result = atomicType("int16", mInt16) + of tyInt32: result = atomicType("int32", mInt32) + of tyInt64: result = atomicType("int64", mInt64) + of tyFloat: result = atomicType("float", mFloat) + of tyFloat32: result = atomicType("float32", mFloat32) + of tyFloat64: result = atomicType("float64", mFloat64) + of tyFloat128: result = atomicType("float128", mFloat128) + of tyUInt: result = atomicType("uint", mUint) + of tyUInt8: result = atomicType("uint8", mUint8) + of tyUInt16: result = atomicType("uint16", mUint16) + of tyUInt32: result = atomicType("uint32", mUint32) + of tyUInt64: result = atomicType("uint64", mUint64) + of tyBigNum: result = atomicType("bignum", mNone) + of tyConst: result = mapTypeToBracket("const", mNone, t, info) + of tyMutable: result = mapTypeToBracket("mutable", mNone, t, info) + of tyVarargs: result = mapTypeToBracket("varargs", mVarargs, t, info) + of tyIter: result = mapTypeToBracket("iter", mNone, t, info) + of tyProxy: result = atomicType("error", mNone) + of tyBuiltInTypeClass: + result = mapTypeToBracket("builtinTypeClass", mNone, t, info) of tyUserTypeClass: - result = mapTypeToBracket("concept", t, info) + result = mapTypeToBracket("concept", mNone, t, info) result.add t.n.copyTree - of tyCompositeTypeClass: result = mapTypeToBracket("compositeTypeClass", t, info) - of tyAnd: result = mapTypeToBracket("and", t, info) - of tyOr: result = mapTypeToBracket("or", t, info) - of tyNot: result = mapTypeToBracket("not", t, info) - of tyAnything: result = atomicType"anything" + of tyCompositeTypeClass: + result = mapTypeToBracket("compositeTypeClass", mNone, t, info) + of tyAnd: result = mapTypeToBracket("and", mAnd, t, info) + of tyOr: result = mapTypeToBracket("or", mOr, t, info) + of tyNot: result = mapTypeToBracket("not", mNot, t, info) + of tyAnything: result = atomicType("anything", mNone) of tyStatic, tyFromExpr, tyFieldAccessor: if inst: if t.n != nil: result = t.n.copyTree - else: result = atomicType "void" + else: result = atomicType("void", mVoid) else: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicType "static" + result.add atomicType("static", mNone) if t.n != nil: result.add t.n.copyTree diff --git a/doc/nep1.rst b/doc/nep1.rst index b4bd6309c..b5991ba9e 100644 --- a/doc/nep1.rst +++ b/doc/nep1.rst @@ -125,8 +125,8 @@ changed in the future. Coding Conventions ------------------ -- The 'return' statement should only be used when it's control-flow properties - are required. Use a procedures implicit 'result' variable instead. This +- The 'return' statement should only be used when its control-flow properties + are required. Use a procedure's implicit 'result' variable instead. This improves readability. - Prefer to return `[]` and `""` instead of `nil`, or throw an exception if @@ -150,7 +150,7 @@ Conventions for multi-line statements and expressions - Any tuple type declarations that are longer than one line should use the regular object type layout instead. This enhances the readability of the - tuple declaration by splitting its members information across multiple lines. + tuple declaration by splitting its members' information across multiple lines. .. code-block:: nim type diff --git a/lib/pure/ioselectors.nim b/lib/pure/ioselectors.nim new file mode 100644 index 000000000..034b182ab --- /dev/null +++ b/lib/pure/ioselectors.nim @@ -0,0 +1,1764 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Eugene Kabanov +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module allows high-level and efficient I/O multiplexing. +## +## Supported OS primitives: ``epoll``, ``kqueue``, ``poll`` and +## Windows ``select``. +## +## To use threadsafe version of this module, it needs to be compiled +## with both ``-d:threadsafe`` and ``--threads:on`` options. +## +## Supported features: files, sockets, pipes, timers, processes, signals +## and user events. +## +## Fully supported OS: MacOSX, FreeBSD, OpenBSD, NetBSD, Linux. +## +## Partially supported OS: Windows (only sockets and user events), +## Solaris (files, sockets, handles and user events). +## +## TODO: ``/dev/poll``, ``event ports`` and filesystem events. + +import os + +const hasThreadSupport = compileOption("threads") and defined(threadsafe) + +const supportedPlatform = defined(macosx) or defined(freebsd) or + defined(netbsd) or defined(openbsd) or + defined(linux) + +const bsdPlatform = defined(macosx) or defined(freebsd) or + defined(netbsd) or defined(openbsd) + +when defined(linux): + import posix, times +elif bsdPlatform: + import posix, kqueue, times +elif defined(windows): + import winlean +else: + import posix + +when defined(nimdoc): + type + Selector*[T] = ref object + ## An object which holds descriptors to be checked for read/write status + + Event* {.pure.} = enum + ## An enum which hold event types + Read, ## Descriptor is available for read + Write, ## Descriptor is available for write + Timer, ## Timer descriptor is completed + Signal, ## Signal is raised + Process, ## Process is finished + Vnode, ## Currently not supported + User, ## User event is raised + Error ## Error happens while waiting, for descriptor + + ReadyKey*[T] = object + ## An object which holds result for descriptor + fd* : int ## file/socket descriptor + events*: set[Event] ## set of events + data*: T ## application-defined data + + SelectEvent* = object + ## An object which holds user defined event + + proc newSelector*[T](): Selector[T] = + ## Creates a new selector + + proc close*[T](s: Selector[T]) = + ## Closes selector + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event], + data: T) = + ## Registers file/socket descriptor ``fd`` to selector ``s`` + ## with events set in ``events``. The ``data`` is application-defined + ## data, which to be passed when event happens. + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, events: set[Event]) = + ## Update file/socket descriptor ``fd``, registered in selector + ## ``s`` with new events set ``event``. + + proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, + data: T): int {.discardable.} = + ## Registers timer notification with ``timeout`` in milliseconds + ## to selector ``s``. + ## If ``oneshot`` is ``true`` timer will be notified only once. + ## Set ``oneshot`` to ``false`` if your want periodic notifications. + ## The ``data`` is application-defined data, which to be passed, when + ## time limit expired. + + proc registerSignal*[T](s: Selector[T], signal: int, + data: T): int {.discardable.} = + ## Registers Unix signal notification with ``signal`` to selector + ## ``s``. The ``data`` is application-defined data, which to be + ## passed, when signal raises. + ## + ## This function is not supported for ``Windows``. + + proc registerProcess*[T](s: Selector[T], pid: int, + data: T): int {.discardable.} = + ## Registers process id (pid) notification when process has + ## exited to selector ``s``. + ## The ``data`` is application-defined data, which to be passed, when + ## process with ``pid`` has exited. + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + ## Registers selector event ``ev`` to selector ``s``. + ## ``data`` application-defined data, which to be passed, when + ## ``ev`` happens. + + proc newEvent*(): SelectEvent = + ## Creates new event ``SelectEvent``. + + proc setEvent*(ev: SelectEvent) = + ## Trigger event ``ev``. + + proc close*(ev: SelectEvent) = + ## Closes selector event ``ev``. + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + ## Unregisters event ``ev`` from selector ``s``. + + proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = + ## Unregisters file/socket descriptor ``fd`` from selector ``s``. + + proc flush*[T](s: Selector[T]) = + ## Flushes all changes was made to kernel pool/queue. + ## This function is usefull only for BSD and MacOS, because + ## kqueue supports bulk changes to be made. + ## On Linux/Windows and other Posix compatible operation systems, + ## ``flush`` is alias for `discard`. + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey[T]]): int = + ## Process call waiting for events registered in selector ``s``. + ## The ``timeout`` argument specifies the minimum number of milliseconds + ## the function will be blocked, if no events are not ready. Specifying a + ## timeout of ``-1`` causes function to block indefinitely. + ## All available events will be stored in ``results`` array. + ## + ## Function returns number of triggered events. + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] = + ## Process call waiting for events registered in selector ``s``. + ## The ``timeout`` argument specifies the minimum number of milliseconds + ## the function will be blocked, if no events are not ready. Specifying a + ## timeout of -1 causes function to block indefinitely. + ## + ## Function returns sequence of triggered events. + + template isEmpty*[T](s: Selector[T]): bool = + ## Returns ``true``, if there no registered events or descriptors + ## in selector. + + template withData*[T](s: Selector[T], fd: SocketHandle, value, + body: untyped) = + ## retrieves the application-data assigned with descriptor ``fd`` + ## to ``value``. This ``value`` can be modified in the scope of + ## the ``withData`` call. + ## + ## .. code-block:: nim + ## + ## s.withData(fd, value) do: + ## # block is executed only if ``fd`` registered in selector ``s`` + ## value.uid = 1000 + ## + + template withData*[T](s: Selector[T], fd: SocketHandle, value, + body1, body2: untyped) = + ## retrieves the application-data assigned with descriptor ``fd`` + ## to ``value``. This ``value`` can be modified in the scope of + ## the ``withData`` call. + ## + ## .. code-block:: nim + ## + ## s.withData(fd, value) do: + ## # block is executed only if ``fd`` registered in selector ``s``. + ## value.uid = 1000 + ## do: + ## # block is executed if ``fd`` not registered in selector ``s``. + ## raise + ## + +else: + when defined(macosx) or defined(freebsd): + when defined(macosx): + const maxDescriptors = 29 # KERN_MAXFILESPERPROC (MacOS) + else: + const maxDescriptors = 27 # KERN_MAXFILESPERPROC (FreeBSD) + proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int, + newp: pointer, newplen: int): cint + {.importc: "sysctl",header: """#include <sys/types.h> + #include <sys/sysctl.h>"""} + elif defined(netbsd) or defined(openbsd): + # OpenBSD and NetBSD don't have KERN_MAXFILESPERPROC, so we are using + # KERN_MAXFILES, because KERN_MAXFILES is always bigger, + # than KERN_MAXFILESPERPROC + const maxDescriptors = 7 # KERN_MAXFILES + proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr int, + newp: pointer, newplen: int): cint + {.importc: "sysctl",header: """#include <sys/param.h> + #include <sys/sysctl.h>"""} + elif defined(linux) or defined(solaris): + proc ulimit(cmd: cint): clong + {.importc: "ulimit", header: "<ulimit.h>", varargs.} + elif defined(windows): + discard + else: + var + RLIMIT_NOFILE {.importc: "RLIMIT_NOFILE", + header: "<sys/resource.h>".}: cint + type + rlimit {.importc: "struct rlimit", + header: "<sys/resource.h>", pure, final.} = object + rlim_cur: int + rlim_max: int + proc getrlimit(resource: cint, rlp: var rlimit): cint + {.importc: "getrlimit",header: "<sys/resource.h>".} + + proc getMaxFds*(): int = + when defined(macosx) or defined(freebsd) or defined(netbsd) or + defined(openbsd): + var count = cint(0) + var size = sizeof(count) + var namearr = [cint(1), cint(maxDescriptors)] + + if sysctl(addr namearr[0], 2, cast[pointer](addr count), addr size, + nil, 0) != 0: + raiseOsError(osLastError()) + result = count + elif defined(linux) or defined(solaris): + result = int(ulimit(4, 0)) + elif defined(windows): + result = FD_SETSIZE + else: + var a = rlimit() + if getrlimit(RLIMIT_NOFILE, a) != 0: + raiseOsError(osLastError()) + result = a.rlim_max + + when hasThreadSupport: + import locks + + type + Event* {.pure.} = enum + Read, Write, Timer, Signal, Process, Vnode, User, Error, + flagHandle, flagTimer, flagSignal, flagProcess, flagVnode, flagUser, + flagOneshot + + ReadyKey*[T] = object + fd* : int + events*: set[Event] + data*: T + + SelectorKey[T] = object + ident : int + flags : set[Event] + param : int + key : ReadyKey[T] + + when not defined(windows): + type + SharedArrayHolder[T] = object + part: array[16, T] + SharedArray {.unchecked.}[T] = array[0..100_000_000, T] + + proc allocSharedArray[T](nsize: int): ptr SharedArray[T] = + let holder = cast[ptr SharedArrayHolder[T]]( + allocShared0(sizeof(T) * nsize) + ) + result = cast[ptr SharedArray[T]](addr(holder.part[0])) + + proc deallocSharedArray[T](sa: ptr SharedArray[T]) = + deallocShared(cast[pointer](sa)) + + template setNonBlocking(fd) = + var x: int = fcntl(fd, F_GETFL, 0) + if x == -1: raiseOSError(osLastError()) + else: + var mode = x or O_NONBLOCK + if fcntl(fd, F_SETFL, mode) == -1: + raiseOSError(osLastError()) + + template setKey(s, f1, f2, e, p, d) = + s.fds[f1].ident = f1 + s.fds[f1].flags = e + s.fds[f1].param = p + s.fds[f1].key.fd = f2 + s.fds[f1].key.data = d + + template clearKey(s, f) = + s.fds[f].ident = 0 + s.fds[f].flags = {} + + template checkMaxFd(s, fd) = + if fd.uint >= s.maxFD: + raise newException(ValueError, "Maximum file descriptors exceeded") + + when supportedPlatform: + template blockSignals(newmask: var Sigset, oldmask: var Sigset) = + when hasThreadSupport: + if posix.pthread_sigmask(SIG_BLOCK, newmask, oldmask) == -1: + raiseOSError(osLastError()) + else: + if posix.sigprocmask(SIG_BLOCK, newmask, oldmask) == -1: + raiseOSError(osLastError()) + + template unblockSignals(newmask: var Sigset, oldmask: var Sigset) = + when hasThreadSupport: + if posix.pthread_sigmask(SIG_UNBLOCK, newmask, oldmask) == -1: + raiseOSError(osLastError()) + else: + if posix.sigprocmask(SIG_UNBLOCK, newmask, oldmask) == -1: + raiseOSError(osLastError()) + # + # BSD kqueue + # + # I have tried to adopt kqueue's EVFILT_USER filter for user-events, but it + # looks not very usable, because of 2 cases: + # 1) EVFILT_USER does not supported by OpenBSD and NetBSD + # 2) You can't have one event, which you can use with many kqueue handles. + # So decision was made in favor of the pipes + # + when bsdPlatform: + const + # Maximum number of cached changes + MAX_KQUEUE_CHANGE_EVENTS = 64 + # Maximum number of events that can be returned + MAX_KQUEUE_RESULT_EVENTS = 64 + + type + SelectorImpl[T] = object + kqFD : cint + maxFD : uint + changesTable: array[MAX_KQUEUE_CHANGE_EVENTS, KEvent] + changesCount: int + fds: ptr SharedArray[SelectorKey[T]] + count: int + when hasThreadSupport: + changesLock: Lock + Selector*[T] = ptr SelectorImpl[T] + + type + SelectEventImpl = object + rfd: cint + wfd: cint + # SelectEvent is declared as `ptr` to be placed in `shared memory`, + # so you can share one SelectEvent handle between threads. + type SelectEvent* = ptr SelectEventImpl + + proc newSelector*[T](): Selector[T] = + var maxFD = getMaxFds() + var kqFD = kqueue() + if kqFD < 0: + raiseOsError(osLastError()) + + result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) + result.kqFD = kqFD + result.maxFD = maxFD.uint + result.fds = allocSharedArray[SelectorKey[T]](maxFD) + when hasThreadSupport: + initLock(result.changesLock) + + proc close*[T](s: Selector[T]) = + if posix.close(s.kqFD) != 0: + raiseOSError(osLastError()) + when hasThreadSupport: + deinitLock(s.changesLock) + deallocSharedArray(s.fds) + deallocShared(cast[pointer](s)) + + when hasThreadSupport: + template withChangeLock[T](s: Selector[T], body: untyped) = + acquire(s.changesLock) + {.locks: [s.changesLock].}: + try: + body + finally: + release(s.changesLock) + else: + template withChangeLock(s, body: untyped) = + body + + template modifyKQueue[T](s: Selector[T], nident: uint, nfilter: cshort, + nflags: cushort, nfflags: cuint, ndata: int, + nudata: pointer) = + mixin withChangeLock + s.withChangeLock(): + s.changesTable[s.changesCount] = KEvent(ident: nident, + filter: nfilter, flags: nflags, + fflags: nfflags, data: ndata, + udata: nudata) + inc(s.changesCount) + if s.changesCount == MAX_KQUEUE_CHANGE_EVENTS: + if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount), + nil, 0, nil) == -1: + raiseOSError(osLastError()) + s.changesCount = 0 + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event], data: T) = + var fdi = int(fd) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, fdi, {Event.flagHandle} + events, 0, data) + if events != {}: + if Event.Read in events: + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil) + inc(s.count) + if Event.Write in events: + modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil) + inc(s.count) + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event]) = + var fdi = int(fd) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident != 0) + doAssert(Event.flagHandle in s.fds[fdi].flags) + var ne = events + {Event.flagHandle} + var oe = s.fds[fdi].flags + if oe != ne: + if (Event.Read in oe) and (Event.Read notin ne): + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil) + dec(s.count) + if (Event.Write in oe) and (Event.Write notin ne): + modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil) + dec(s.count) + if (Event.Read notin oe) and (Event.Read in ne): + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil) + inc(s.count) + if (Event.Write notin oe) and (Event.Write in ne): + modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_ADD, 0, 0, nil) + inc(s.count) + s.fds[fdi].flags = ne + + proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, + data: T): int {.discardable.} = + var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM, + posix.IPPROTO_TCP).int + if fdi == -1: + raiseOsError(osLastError()) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + var mflags = if oneshot: {Event.flagTimer, Event.flagOneshot} + else: {Event.flagTimer} + var kflags: cushort = if oneshot: EV_ONESHOT or EV_ADD + else: EV_ADD + setKey(s, fdi, fdi, mflags, 0, data) + # EVFILT_TIMER on Open/Net(BSD) has granularity of only milliseconds, + # but MacOS and FreeBSD allow use `0` as `fflags` to use milliseconds + # too + modifyKQueue(s, fdi.uint, EVFILT_TIMER, kflags, 0, cint(timeout), nil) + inc(s.count) + result = fdi + + proc registerSignal*[T](s: Selector[T], signal: int, + data: T): int {.discardable.} = + var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM, + posix.IPPROTO_TCP).int + if fdi == -1: + raiseOsError(osLastError()) + + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, signal, {Event.flagSignal}, signal, data) + # block signal `signal` + var nmask: Sigset + var omask: Sigset + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, cint(signal)) + blockSignals(nmask, omask) + # to be compatible with linux semantic we need to "eat" signals + posix.signal(cint(signal), SIG_IGN) + modifyKQueue(s, signal.uint, EVFILT_SIGNAL, EV_ADD, 0, 0, + cast[pointer](fdi)) + inc(s.count) + result = fdi + + proc registerProcess*[T](s: Selector[T], pid: int, + data: T): int {.discardable.} = + var fdi = posix.socket(posix.AF_INET, posix.SOCK_STREAM, + posix.IPPROTO_TCP).int + if fdi == -1: + raiseOsError(osLastError()) + + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + var kflags: cushort = EV_ONESHOT or EV_ADD + setKey(s, fdi, pid, {Event.flagProcess, Event.flagOneshot}, pid, data) + modifyKQueue(s, pid.uint, EVFILT_PROC, kflags, NOTE_EXIT, 0, + cast[pointer](fdi)) + inc(s.count) + result = fdi + + proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = + var fdi = int(fd) + if fdi.uint < s.maxFD: + var flags = s.fds[fdi].flags + var filter: cshort = 0 + if s.fds[fdi].ident != 0 and flags != {}: + if Event.flagHandle in flags: + # if events == 0, than descriptor was modified with + # updateHandle(fd, 0), so it was already deleted from kqueue. + if flags != {Event.flagHandle}: + if Event.Read in flags: + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil) + dec(s.count) + if Event.Write in flags: + modifyKQueue(s, fdi.uint, EVFILT_WRITE, EV_DELETE, 0, 0, nil) + dec(s.count) + elif Event.flagTimer in flags: + filter = EVFILT_TIMER + discard posix.close(cint(s.fds[fdi].key.fd)) + modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil) + dec(s.count) + elif Event.flagSignal in flags: + filter = EVFILT_SIGNAL + # unblocking signal + var nmask = Sigset() + var omask = Sigset() + var signal = cint(s.fds[fdi].param) + discard sigaddset(nmask, signal) + unblockSignals(nmask, omask) + posix.signal(signal, SIG_DFL) + discard posix.close(cint(s.fds[fdi].key.fd)) + modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil) + dec(s.count) + elif Event.flagProcess in flags: + filter = EVFILT_PROC + discard posix.close(cint(s.fds[fdi].key.fd)) + modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil) + dec(s.count) + elif Event.flagUser in flags: + filter = EVFILT_READ + modifyKQueue(s, fdi.uint, filter, EV_DELETE, 0, 0, nil) + dec(s.count) + clearKey(s, fdi) + + proc flush*[T](s: Selector[T]) = + s.withChangeLock(): + var tv = Timespec() + if kevent(s.kqFD, addr(s.changesTable[0]), cint(s.changesCount), + nil, 0, addr tv) == -1: + raiseOSError(osLastError()) + s.changesCount = 0 + + template isEmpty*[T](s: Selector[T]): bool = + (s.count == 0) + + proc newEvent*(): SelectEvent = + var fds: array[2, cint] + + if posix.pipe(fds) == -1: + raiseOSError(osLastError()) + + setNonBlocking(fds[0]) + setNonBlocking(fds[1]) + + result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl))) + result.rfd = fds[0] + result.wfd = fds[1] + + proc setEvent*(ev: SelectEvent) = + var data: int = 1 + if posix.write(ev.wfd, addr data, sizeof(int)) != sizeof(int): + raiseOSError(osLastError()) + + proc close*(ev: SelectEvent) = + discard posix.close(cint(ev.rfd)) + discard posix.close(cint(ev.wfd)) + deallocShared(cast[pointer](ev)) + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + let fdi = ev.rfd.int + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, fdi, {Event.flagUser}, 0, data) + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_ADD, 0, 0, nil) + inc(s.count) + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + let fdi = ev.rfd.int + var flags = s.fds[fdi].flags + if s.fds[fdi].ident != 0 and flags != {}: + modifyKQueue(s, fdi.uint, EVFILT_READ, EV_DELETE, 0, 0, nil) + dec(s.count) + clearKey(s, fdi) + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey[T]]): int = + var + tv: Timespec + resultsTable: array[MAX_KQUEUE_RESULT_EVENTS, KEvent] + ptv: ptr Timespec = addr tv + + if timeout != -1: + if timeout >= 1000: + tv.tv_sec = (timeout div 1_000).Time + tv.tv_nsec = (timeout %% 1_000) * 1_000_000 + else: + tv.tv_sec = 0.Time + tv.tv_nsec = timeout * 1_000_000 + else: + ptv = nil + + var maxResults = MAX_KQUEUE_RESULT_EVENTS + if maxResults > len(results): + maxResults = len(results) + + var count = 0 + s.withChangeLock(): + count = kevent(s.kqFD, + addr(s.changesTable[0]), cint(s.changesCount), + addr(resultsTable[0]), cint(maxResults), ptv) + s.changesCount = 0 + if count >= 0: + var skey: ptr SelectorKey[T] + var i = 0 + var k = 0 + while i < count: + var kevent = addr(resultsTable[i]) + if (kevent.flags and EV_ERROR) == 0: + var events: set[Event] = {} + case kevent.filter + of EVFILT_READ: + skey = addr(s.fds[kevent.ident.int]) + if Event.flagHandle in skey.flags: + events = {Event.Read} + elif Event.flagUser in skey.flags: + var data: int = 0 + if posix.read(kevent.ident.cint, addr data, + sizeof(int)) != sizeof(int): + let err = osLastError() + if err == OSErrorCode(EAGAIN): + # someone already consumed event data + inc(i) + continue + else: + raiseOSError(osLastError()) + events = {Event.User} + else: + events = {Event.Read} + of EVFILT_WRITE: + skey = addr(s.fds[kevent.ident.int]) + events = {Event.Write} + of EVFILT_TIMER: + skey = addr(s.fds[kevent.ident.int]) + if Event.flagOneshot in skey.flags: + if posix.close(skey.ident.cint) == -1: + raiseOSError(osLastError()) + clearKey(s, skey.ident) + # no need to modify kqueue, because EV_ONESHOT is already made + # this for us + dec(s.count) + events = {Event.Timer} + of EVFILT_VNODE: + skey = addr(s.fds[kevent.ident.int]) + events = {Event.Vnode} + of EVFILT_SIGNAL: + skey = addr(s.fds[cast[int](kevent.udata)]) + events = {Event.Signal} + of EVFILT_PROC: + skey = addr(s.fds[cast[int](kevent.udata)]) + if posix.close(skey.ident.cint) == -1: + raiseOSError(osLastError()) + clearKey(s, skey.ident) + # no need to modify kqueue, because EV_ONESHOT is already made + # this for us + dec(s.count) + events = {Event.Process} + else: + raise newException(ValueError, + "Unsupported kqueue filter in queue") + + if (kevent.flags and EV_EOF) != 0: + events = events + {Event.Error} + results[k].fd = skey.key.fd + results[k].events = events + results[k].data = skey.key.data + inc(k) + inc(i) + result = k + else: + result = 0 + let err = osLastError() + if cint(err) != EINTR: + raiseOSError(err) + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] = + result = newSeq[ReadyKey[T]](MAX_KQUEUE_RESULT_EVENTS) + var count = selectInto(s, timeout, result) + result.setLen(count) + + # + # Linux epoll + # + + elif defined(linux): + const + # Maximum number of events that can be returned + MAX_EPOLL_RESULT_EVENTS = 64 + type + SignalFdInfo* {.importc: "struct signalfd_siginfo", + header: "<sys/signalfd.h>", pure, final.} = object + ssi_signo*: uint32 + ssi_errno*: int32 + ssi_code*: int32 + ssi_pid*: uint32 + ssi_uid*: uint32 + ssi_fd*: int32 + ssi_tid*: uint32 + ssi_band*: uint32 + ssi_overrun*: uint32 + ssi_trapno*: uint32 + ssi_status*: int32 + ssi_int*: int32 + ssi_ptr*: uint64 + ssi_utime*: uint64 + ssi_stime*: uint64 + ssi_addr*: uint64 + pad* {.importc: "__pad".}: array[0..47, uint8] + type + eventFdData {.importc: "eventfd_t", + header: "<sys/eventfd.h>", pure, final.} = uint64 + epoll_data {.importc: "union epoll_data", + header: "<sys/epoll.h>", + pure, final.} = object + u64 {.importc: "u64".}: uint64 + + epoll_event {.importc: "struct epoll_event", + header: "<sys/epoll.h>", pure, final.} = object + events: uint32 # Epoll events + data: epoll_data # User data variable + const + EPOLL_CTL_ADD = 1 # Add a file descriptor to the interface. + EPOLL_CTL_DEL = 2 # Remove a file descriptor from the interface. + EPOLL_CTL_MOD = 3 # Change file descriptor epoll_event structure. + const + EPOLLIN = 0x00000001 + EPOLLOUT = 0x00000004 + EPOLLERR = 0x00000008 + EPOLLHUP = 0x00000010 + EPOLLRDHUP = 0x00002000 + EPOLLONESHOT = 1 shl 30 + + proc epoll_create(size: cint): cint + {.importc: "epoll_create", header: "<sys/epoll.h>".} + proc epoll_ctl(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint + {.importc: "epoll_ctl", header: "<sys/epoll.h>".} + proc epoll_wait(epfd: cint; events: ptr epoll_event; maxevents: cint; + timeout: cint): cint + {.importc: "epoll_wait", header: "<sys/epoll.h>".} + proc timerfd_create(clock_id: ClockId, flags: cint): cint + {.cdecl, importc: "timerfd_create", header: "<sys/timerfd.h>".} + proc timerfd_settime(ufd: cint, flags: cint, + utmr: var Itimerspec, otmr: var Itimerspec): cint + {.cdecl, importc: "timerfd_settime", header: "<sys/timerfd.h>".} + proc signalfd(fd: cint, mask: var Sigset, flags: cint): cint + {.cdecl, importc: "signalfd", header: "<sys/signalfd.h>".} + proc eventfd(count: cuint, flags: cint): cint + {.cdecl, importc: "eventfd", header: "<sys/eventfd.h>".} + + type + SelectorImpl[T] = object + epollFD : cint + maxFD : uint + fds: ptr SharedArray[SelectorKey[T]] + count: int + + Selector*[T] = ptr SelectorImpl[T] + + SelectEventImpl = object + efd: cint + + SelectEvent* = ptr SelectEventImpl + + proc newSelector*[T](): Selector[T] = + var maxFD = getMaxFds() + var epollFD = epoll_create(MAX_EPOLL_RESULT_EVENTS) + if epollFD < 0: + raiseOsError(osLastError()) + + result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) + result.epollFD = epollFD + result.maxFD = maxFD.uint + result.fds = allocSharedArray[SelectorKey[T]](maxFD) + + proc close*[T](s: Selector[T]) = + if posix.close(s.epollFD) != 0: + raiseOSError(osLastError()) + deallocSharedArray(s.fds) + deallocShared(cast[pointer](s)) + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event], data: T) = + var fdi = int(fd) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, fdi, events + {Event.flagHandle}, 0, data) + if events != {}: + var epv: epoll_event + epv.events = EPOLLRDHUP + epv.data.u64 = fdi.uint + if Event.Read in events: + epv.events = epv.events or EPOLLIN + if Event.Write in events: + epv.events = epv.events or EPOLLOUT + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + inc(s.count) + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event]) = + var fdi = int(fd) + s.checkMaxFd(fdi) + var oe = s.fds[fdi].flags + doAssert(s.fds[fdi].ident != 0) + doAssert(Event.flagHandle in oe) + var ne = events + {Event.flagHandle} + if oe != ne: + var epv: epoll_event + epv.data.u64 = fdi.uint + epv.events = EPOLLRDHUP + + if Event.Read in events: + epv.events = epv.events or EPOLLIN + if Event.Write in events: + epv.events = epv.events or EPOLLOUT + + if oe == {Event.flagHandle}: + if ne != {Event.flagHandle}: + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, + addr epv) == -1: + raiseOSError(osLastError()) + inc(s.count) + else: + if ne != {Event.flagHandle}: + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fdi.cint, + addr epv) == -1: + raiseOSError(osLastError()) + else: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, + addr epv) == -1: + raiseOSError(osLastError()) + dec(s.count) + s.fds[fdi].flags = ne + + proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = + var epv: epoll_event + var fdi = int(fd) + if fdi.uint < s.maxFD: + var flags = s.fds[fdi].flags + if s.fds[fdi].ident != 0 and flags != {}: + if Event.flagHandle in flags: + # if events == {flagHandle}, then descriptor was already + # unregistered from epoll with updateHandle() call. + # This check is done to omit EBADF error. + if flags != {Event.flagHandle}: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, + addr epv) == -1: + raiseOSError(osLastError()) + dec(s.count) + elif Event.flagTimer in flags: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + discard posix.close(fdi.cint) + dec(s.count) + elif Event.flagSignal in flags: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + var nmask: Sigset + var omask: Sigset + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, cint(s.fds[fdi].param)) + unblockSignals(nmask, omask) + discard posix.close(fdi.cint) + dec(s.count) + elif Event.flagProcess in flags: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + var nmask: Sigset + var omask: Sigset + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, SIGCHLD) + unblockSignals(nmask, omask) + discard posix.close(fdi.cint) + dec(s.count) + clearKey(s, fdi) + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + let fdi = int(ev.efd) + if fdi.uint < s.maxFD: + if s.fds[fdi].ident != 0 and (Event.flagUser in s.fds[fdi].flags): + clearKey(s, fdi) + var epv: epoll_event + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + dec(s.count) + + proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, + data: T): int {.discardable.} = + var + new_ts: Itimerspec + old_ts: Itimerspec + var fdi = timerfd_create(CLOCK_MONOTONIC, 0) + if fdi == -1: + raiseOSError(osLastError()) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + var flags = {Event.flagTimer} + var epv: epoll_event + epv.data.u64 = fdi.uint + epv.events = EPOLLIN or EPOLLRDHUP + setNonBlocking(fdi.cint) + if oneshot: + new_ts.it_interval.tv_sec = 0.Time + new_ts.it_interval.tv_nsec = 0 + new_ts.it_value.tv_sec = (timeout div 1_000).Time + new_ts.it_value.tv_nsec = (timeout %% 1_000) * 1_000_000 + flags = flags + {Event.flagOneshot} + epv.events = epv.events or EPOLLONESHOT + else: + new_ts.it_interval.tv_sec = (timeout div 1000).Time + new_ts.it_interval.tv_nsec = (timeout %% 1_000) * 1_000_000 + new_ts.it_value.tv_sec = new_ts.it_interval.tv_sec + new_ts.it_value.tv_nsec = new_ts.it_interval.tv_nsec + if timerfd_settime(fdi.cint, cint(0), new_ts, old_ts) == -1: + raiseOSError(osLastError()) + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + setKey(s, fdi, fdi, flags, 0, data) + inc(s.count) + result = fdi + + proc registerSignal*[T](s: Selector[T], signal: int, + data: T): int {.discardable.} = + var + nmask: Sigset + omask: Sigset + + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, cint(signal)) + blockSignals(nmask, omask) + + var fdi = signalfd(-1, nmask, 0).int + if fdi == -1: + raiseOSError(osLastError()) + + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setNonBlocking(fdi.cint) + + var epv: epoll_event + epv.data.u64 = fdi.uint + epv.events = EPOLLIN or EPOLLRDHUP + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + setKey(s, fdi, signal, {Event.flagSignal}, signal, data) + inc(s.count) + result = fdi + + proc registerProcess*[T](s: Selector, pid: int, + data: T): int {.discardable.} = + var + nmask: Sigset + omask: Sigset + + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, posix.SIGCHLD) + blockSignals(nmask, omask) + + var fdi = signalfd(-1, nmask, 0).int + if fdi == -1: + raiseOSError(osLastError()) + + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setNonBlocking(fdi.cint) + + var epv: epoll_event + epv.data.u64 = fdi.uint + epv.events = EPOLLIN or EPOLLRDHUP + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fdi.cint, addr epv) == -1: + raiseOSError(osLastError()) + setKey(s, fdi, pid, {Event.flagProcess}, pid, data) + inc(s.count) + result = fdi + + proc flush*[T](s: Selector[T]) = + discard + + template isEmpty*[T](s: Selector[T]): bool = + (s.count == 0) + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + let fdi = int(ev.efd) + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, fdi, {Event.flagUser}, 0, data) + var epv = epoll_event(events: EPOLLIN or EPOLLRDHUP) + epv.data.u64 = ev.efd.uint + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, ev.efd, addr epv) == -1: + raiseOSError(osLastError()) + inc(s.count) + + proc setEvent*(ev: SelectEvent) = + var data : uint64 = 1 + if posix.write(ev.efd, addr data, sizeof(uint64)) == -1: + raiseOSError(osLastError()) + + proc close*(ev: SelectEvent) = + discard posix.close(ev.efd) + deallocShared(cast[pointer](ev)) + + proc newEvent*(): SelectEvent = + var fdi = eventfd(0, 0) + if fdi == -1: + raiseOSError(osLastError()) + setNonBlocking(fdi) + result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl))) + result.efd = cint(fdi) + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey[T]]): int = + var + resultsTable: array[MAX_EPOLL_RESULT_EVENTS, epoll_event] + + var maxResults = MAX_EPOLL_RESULT_EVENTS + if maxResults > len(results): + maxResults = len(results) + + var count = epoll_wait(s.epollFD, addr(resultsTable[0]), maxResults.cint, + timeout.cint) + if count > 0: + var i = 0 + var k = 0 + while i < count: + var events: set[Event] = {} + let fdi = int(resultsTable[i].data.u64) + var skey = addr(s.fds[fdi]) + let pevents = resultsTable[i].events + var flags = s.fds[fdi].flags + + if skey.ident != 0 and flags != {}: + block processItem: + if (pevents and EPOLLERR) != 0 or (pevents and EPOLLHUP) != 0: + events = events + {Event.Error} + if (pevents and EPOLLOUT) != 0: + events = events + {Event.Write} + if (pevents and EPOLLIN) != 0: + if Event.flagHandle in flags: + events = events + {Event.Read} + elif Event.flagTimer in flags: + var data: uint64 = 0 + if posix.read(fdi.cint, addr data, + sizeof(uint64)) != sizeof(uint64): + raiseOSError(osLastError()) + events = events + {Event.Timer} + elif Event.flagSignal in flags: + var data: SignalFdInfo + if posix.read(fdi.cint, addr data, + sizeof(SignalFdInfo)) != sizeof(SignalFdInfo): + raiseOsError(osLastError()) + events = events + {Event.Signal} + elif Event.flagProcess in flags: + var data: SignalFdInfo + if posix.read(fdi.cint, addr data, + sizeof(SignalFdInfo)) != sizeof(SignalFdInfo): + raiseOsError(osLastError()) + if cast[int](data.ssi_pid) == skey.param: + events = events + {Event.Process} + # we want to free resources for this event + flags = flags + {Event.flagOneshot} + else: + break processItem + elif Event.flagUser in flags: + var data: uint = 0 + if posix.read(fdi.cint, addr data, + sizeof(uint)) != sizeof(uint): + let err = osLastError() + if err == OSErrorCode(EAGAIN): + # someone already consumed event data + inc(i) + continue + else: + raiseOSError(err) + events = events + {Event.User} + else: + raise newException(ValueError, + "Unsupported epoll event in queue") + results[k].fd = skey.key.fd + results[k].events = events + results[k].data = skey.key.data + + if Event.flagOneshot in flags: + var epv: epoll_event + try: + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fdi.cint, + addr epv) == -1: + raiseOSError(osLastError()) + finally: + discard posix.close(fdi.cint) + s.fds[fdi].ident = 0 + s.fds[fdi].flags = {} + dec(s.count) + inc(k) + inc(i) + result = k + elif count == 0: + discard + else: + result = 0 + let err = osLastError() + if cint(err) != EINTR: + raiseOSError(err) + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] = + result = newSeq[ReadyKey[T]](MAX_EPOLL_RESULT_EVENTS) + var count = selectInto(s, timeout, result) + result.setLen(count) + + # + # Windows select + # + + elif defined(windows): + const FD_SETSIZE = 64 + + import hashes, nativesockets + + when hasThreadSupport: + import sharedtables + else: + import tables + + proc hash*(x: SocketHandle): Hash {.borrow.} + proc `$`*(x: SocketHandle): string {.borrow.} + + proc WSAFDIsSet(s: SocketHandle, fdSet: var TFdSet): bool {. + stdcall, importc: "__WSAFDIsSet", dynlib: "ws2_32.dll", noSideEffect.} + + template iFD_ISSET(s: SocketHandle, fdSet: var TFdSet): bool = + if WSAFDIsSet(s, fdSet): true else: false + + template iFD_SET(s: SocketHandle, fdSet: var TFdSet) = + block: + var i = 0 + while i < fdSet.fd_count: + if fdSet.fd_array[i] == s: + break + inc(i) + if i == fdSet.fd_count: + if fdSet.fd_count < ioselectors.FD_SETSIZE: + fdSet.fd_array[i] = s + inc(fdSet.fd_count) + + template iFD_CLR(s: SocketHandle, fdSet: var TFdSet) = + block: + var i = 0 + while i < fdSet.fd_count: + if fdSet.fd_array[i] == s: + if i == fdSet.fd_count - 1: + fdSet.fd_array[i] = 0.SocketHandle + else: + while i < (fdSet.fd_count - 1): + fdSet.fd_array[i] = fdSet.fd_array[i + 1] + inc(i) + dec(fdSet.fd_count) + break + inc(i) + + template iFD_ZERO(fdSet: var TFdSet) = + fdSet.fd_count = 0 + + when hasThreadSupport: + type + SelectorImpl[T] = object + rSet: TFdSet + wSet: TFdSet + eSet: TFdSet + maxFD: uint + fds: SharedTable[SocketHandle, SelectorKey[T]] + count: int + lock: Lock + else: + type + SelectorImpl[T] = object + rSet: TFdSet + wSet: TFdSet + eSet: TFdSet + maxFD: uint + fds: Table[SocketHandle, SelectorKey[T]] + count: int + + when hasThreadSupport: + type Selector*[T] = ptr SelectorImpl[T] + else: + type Selector*[T] = ref SelectorImpl[T] + + type + SelectEventImpl = object + rsock: SocketHandle + wsock: SocketHandle + + type SelectEvent* = ptr SelectEventImpl + + when hasThreadSupport: + template withSelectLock[T](s: Selector[T], body: untyped) = + acquire(s.lock) + {.locks: [s.lock].}: + try: + body + finally: + release(s.lock) + else: + template withSelectLock[T](s: Selector[T], body: untyped) = + body + + proc newSelector*[T](): Selector[T] = + var maxFD = FD_SETSIZE + when hasThreadSupport: + result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) + result.maxFD = maxFD.uint + result.fds = initSharedTable[SocketHandle, SelectorKey[T]]() + initLock result.lock + else: + result = Selector[T](maxFD: FD_SETSIZE) + result.maxFD = maxFD.uint + result.fds = initTable[SocketHandle, SelectorKey[T]]() + + iFD_ZERO(result.rSet) + iFD_ZERO(result.wSet) + iFD_ZERO(result.eSet) + + proc close*(s: Selector) = + when hasThreadSupport: + deinitSharedTable(s.fds) + deallocShared(cast[pointer](s)) + + template isEmpty*[T](s: Selector[T]): bool = + (s.count == 0) + + template selectAdd[T](s: Selector[T], fd: SocketHandle, + events: set[Event]) = + mixin withSelectLock + s.withSelectLock(): + if Event.Read in events: + if s.rSet.fd_count == FD_SETSIZE: + raise newException(ValueError, "Maximum numbers of fds exceeded") + iFD_SET(fd, s.rSet) + inc(s.count) + if Event.Write in events: + if s.wSet.fd_count == FD_SETSIZE: + raise newException(ValueError, "Maximum numbers of fds exceeded") + iFD_SET(fd, s.wSet) + iFD_SET(fd, s.eSet) + inc(s.count) + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event], data: T) = + var fdi = int(fd) + var flags = {Event.flagHandle} + events + var nkey = SelectorKey[T](ident: fdi, flags: flags) + nkey.key.fd = fdi + nkey.key.data = data + + if s.fds.hasKeyOrPut(fd, nkey): + raise newException(ValueError, "Re-use of non closed descriptor") + selectAdd(s, fd, flags) + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event]) = + s.withSelectLock(): + withValue(s.fds, fd, skey) do: + if Event.flagHandle in skey.flags: + var oe = skey.flags + var ne = events + {Event.flagHandle} + if oe != ne: + if (Event.Read in oe) and (Event.Read notin ne): + iFD_CLR(fd, s.rSet) + dec(s.count) + if (Event.Write in oe) and (Event.Write notin ne): + iFD_CLR(fd, s.wSet) + iFD_CLR(fd, s.eSet) + dec(s.count) + if (Event.Read notin oe) and (Event.Read in ne): + iFD_SET(fd, s.rSet) + inc(s.count) + if (Event.Write notin oe) and (Event.Write in ne): + iFD_SET(fd, s.wSet) + iFD_SET(fd, s.eSet) + inc(s.count) + skey.flags = ne + else: + raise newException(ValueError, + "Could not update non-handle descriptor") + do: + raise newException(ValueError, + "Descriptor is not registered in queue") + + proc registerTimer*[T](s: Selector, timeout: int, oneshot: bool, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc registerSignal*[T](s: Selector, signal: int, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc registerProcess*[T](s: Selector, pid: int, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc flush*[T](s: Selector[T]) = discard + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + let fd = ev.rsock + s.withSelectLock(): + iFD_CLR(fd, s.rSet) + dec(s.count) + s.fds.del(fd) + + + proc unregister*[T](s: Selector[T], fd: SocketHandle) = + s.withSelectLock(): + s.fds.withValue(fd, skey) do: + if Event.Read in skey.flags: + iFD_CLR(fd, s.rSet) + dec(s.count) + if Event.Write in skey.flags: + iFD_CLR(fd, s.wSet) + iFD_CLR(fd, s.eSet) + dec(s.count) + s.fds.del(fd) + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + var flags = {Event.flagUser, Event.Read} + var nkey = SelectorKey[T](ident: ev.rsock.int, flags: flags) + nkey.key.fd = ev.rsock.int + nkey.key.data = data + if s.fds.hasKeyOrPut(ev.rsock, nkey): + raise newException(ValueError, "Re-use of non closed descriptor") + selectAdd(s, ev.rsock, flags) + + proc newEvent*(): SelectEvent = + var ssock = newNativeSocket() + var wsock = newNativeSocket() + var rsock: SocketHandle = INVALID_SOCKET + var saddr = Sockaddr_in() + try: + saddr.sin_family = winlean.AF_INET + saddr.sin_port = 0 + saddr.sin_addr.s_addr = INADDR_ANY + if bindAddr(ssock, cast[ptr SockAddr](addr(saddr)), + sizeof(saddr).SockLen) < 0'i32: + raiseOSError(osLastError()) + + if winlean.listen(ssock, 1) == -1: + raiseOSError(osLastError()) + + var namelen = sizeof(saddr).SockLen + if getsockname(ssock, cast[ptr SockAddr](addr(saddr)), + addr(namelen)) == -1'i32: + raiseOSError(osLastError()) + + saddr.sin_addr.s_addr = 0x0100007F + if winlean.connect(wsock, cast[ptr SockAddr](addr(saddr)), + sizeof(saddr).SockLen) == -1: + raiseOSError(osLastError()) + namelen = sizeof(saddr).SockLen + rsock = winlean.accept(ssock, cast[ptr SockAddr](addr(saddr)), + cast[ptr SockLen](addr(namelen))) + if rsock == SocketHandle(-1): + raiseOSError(osLastError()) + + if winlean.closesocket(ssock) == -1: + raiseOSError(osLastError()) + + var mode = clong(1) + if ioctlsocket(rsock, FIONBIO, addr(mode)) == -1: + raiseOSError(osLastError()) + mode = clong(1) + if ioctlsocket(wsock, FIONBIO, addr(mode)) == -1: + raiseOSError(osLastError()) + + result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl))) + result.rsock = rsock + result.wsock = wsock + except: + discard winlean.closesocket(ssock) + discard winlean.closesocket(wsock) + if rsock != INVALID_SOCKET: + discard winlean.closesocket(rsock) + + proc setEvent*(ev: SelectEvent) = + var data: int = 1 + if winlean.send(ev.wsock, cast[pointer](addr data), + cint(sizeof(int)), 0) != sizeof(int): + raiseOSError(osLastError()) + + proc close*(ev: SelectEvent) = + discard winlean.closesocket(ev.rsock) + discard winlean.closesocket(ev.wsock) + deallocShared(cast[pointer](ev)) + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey[T]]): int = + var tv = Timeval() + var ptv = addr tv + var rset, wset, eset: TFdSet + + if timeout != -1: + tv.tv_sec = timeout.int32 div 1_000 + tv.tv_usec = (timeout.int32 %% 1_000) * 1_000 + else: + ptv = nil + + s.withSelectLock(): + rset = s.rSet + wset = s.wSet + eset = s.eSet + + var count = select(cint(0), addr(rset), addr(wset), + addr(eset), ptv).int + if count > 0: + var rindex = 0 + var i = 0 + while i < rset.fd_count: + let fd = rset.fd_array[i] + if iFD_ISSET(fd, rset): + var events = {Event.Read} + if iFD_ISSET(fd, eset): events = events + {Event.Error} + if iFD_ISSET(fd, wset): events = events + {Event.Write} + s.fds.withValue(fd, skey) do: + if Event.flagHandle in skey.flags: + skey.key.events = events + elif Event.flagUser in skey.flags: + var data: int = 0 + if winlean.recv(fd, cast[pointer](addr(data)), + sizeof(int).cint, 0) != sizeof(int): + let err = osLastError() + if err != OSErrorCode(WSAEWOULDBLOCK): + raiseOSError(err) + else: + # someone already consumed event data + inc(i) + continue + skey.key.events = {Event.User} + results[rindex].fd = skey.key.fd + results[rindex].data = skey.key.data + results[rindex].events = skey.key.events + inc(rindex) + inc(i) + + i = 0 + while i < wset.fd_count: + let fd = wset.fd_array[i] + if iFD_ISSET(fd, wset): + var events = {Event.Write} + if not iFD_ISSET(fd, rset): + if iFD_ISSET(fd, eset): events = events + {Event.Error} + s.fds.withValue(fd, skey) do: + skey.key.events = events + results[rindex].fd = skey.key.fd + results[rindex].data = skey.key.data + results[rindex].events = skey.key.events + inc(rindex) + inc(i) + count = rindex + elif count == 0: + discard + else: + raiseOSError(osLastError()) + result = count + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] = + result = newSeq[ReadyKey[T]](FD_SETSIZE) + var count = selectInto(s, timeout, result) + result.setLen(count) + + # + # Posix poll + # + + else: + # Maximum number of events that can be returned + const MAX_POLL_RESULT_EVENTS = 64 + + type + SelectorImpl[T] = object + maxFD : uint + pollcnt: int + fds: ptr SharedArray[SelectorKey[T]] + pollfds: ptr SharedArray[TPollFd] + count: int + when hasThreadSupport: + lock: Lock + + Selector*[T] = ptr SelectorImpl[T] + + SelectEventImpl = object + rfd: cint + wfd: cint + + SelectEvent* = ptr SelectEventImpl + + when hasThreadSupport: + template withPollLock[T](s: Selector[T], body: untyped) = + acquire(s.lock) + {.locks: [s.lock].}: + try: + body + finally: + release(s.lock) + else: + template withPollLock(s, body: untyped) = + body + + proc newSelector*[T](): Selector[T] = + var maxFD = getMaxFds() + + result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) + result.maxFD = maxFD.uint + result.fds = allocSharedArray[SelectorKey[T]](maxFD) + result.pollfds = allocSharedArray[TPollFd](maxFD) + when hasThreadSupport: + initLock(result.lock) + + proc close*[T](s: Selector[T]) = + when hasThreadSupport: + deinitLock(s.lock) + deallocSharedArray(s.fds) + deallocSharedArray(s.pollfds) + deallocShared(cast[pointer](s)) + + template pollAdd[T](s: Selector[T], sock: cint, events: set[Event]) = + withPollLock(s): + var pollev: cshort = 0 + if Event.Read in events: pollev = pollev or POLLIN + if Event.Write in events: pollev = pollev or POLLOUT + s.pollfds[s.pollcnt].fd = cint(sock) + s.pollfds[s.pollcnt].events = pollev + inc(s.count) + inc(s.pollcnt) + + template pollUpdate[T](s: Selector[T], sock: cint, events: set[Event]) = + withPollLock(s): + var i = 0 + var pollev: cshort = 0 + if Event.Read in events: pollev = pollev or POLLIN + if Event.Write in events: pollev = pollev or POLLOUT + + while i < s.pollcnt: + if s.pollfds[i].fd == sock: + s.pollfds[i].events = pollev + break + inc(i) + + if i == s.pollcnt: + raise newException(ValueError, + "Descriptor is not registered in queue") + + template pollRemove[T](s: Selector[T], sock: cint) = + withPollLock(s): + var i = 0 + while i < s.pollcnt: + if s.pollfds[i].fd == sock: + if i == s.pollcnt - 1: + s.pollfds[i].fd = 0 + s.pollfds[i].events = 0 + s.pollfds[i].revents = 0 + else: + while i < (s.pollcnt - 1): + s.pollfds[i].fd = s.pollfds[i + 1].fd + s.pollfds[i].events = s.pollfds[i + 1].events + inc(i) + break + inc(i) + dec(s.pollcnt) + dec(s.count) + + proc registerHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event], data: T) = + var fdi = int(fd) + s.checkMaxFd(fdi) + doAssert(s.fds[fdi].ident == 0) + setKey(s, fdi, fdi, {Event.flagHandle} + events, 0, data) + s.pollAdd(fdi.cint, events) + + proc updateHandle*[T](s: Selector[T], fd: SocketHandle, + events: set[Event]) = + var fdi = int(fd) + s.checkMaxFd(fdi) + var oe = s.fds[fdi].flags + doAssert(s.fds[fdi].ident != 0) + doAssert(Event.flagHandle in oe) + var ne = events + {Event.flagHandle} + if ne != oe: + if events != {}: + s.pollUpdate(fd.cint, events) + else: + s.pollRemove(fd.cint) + s.fds[fdi].flags = ne + + proc registerTimer*[T](s: Selector[T], timeout: int, oneshot: bool, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc registerSignal*[T](s: Selector[T], signal: int, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc registerProcess*[T](s: Selector[T], pid: int, + data: T): int {.discardable.} = + raise newException(ValueError, "Not implemented") + + proc registerEvent*[T](s: Selector[T], ev: SelectEvent, data: T) = + var fdi = int(ev.rfd) + doAssert(s.fds[fdi].ident == 0) + var events = {Event.flagUser, Event.Read} + setKey(s, fdi, fdi, events, 0, data) + s.pollAdd(fdi.cint, events) + + proc flush*[T](s: Selector[T]) = discard + + template isEmpty*[T](s: Selector[T]): bool = + (s.count == 0) + + proc unregister*[T](s: Selector[T], fd: int|SocketHandle|cint) = + var fdi = int(fd) + if fdi.uint < s.maxFD: + if s.fds[fdi].ident != 0 and s.fds[fdi].flags != {}: + clearKey(s, fdi) + s.pollRemove(fdi.cint) + + proc unregister*[T](s: Selector[T], ev: SelectEvent) = + var fdi = int(ev.rfd) + if fdi.uint < s.maxFD: + if s.fds[fdi].ident != 0 and (Event.flagUser in s.fds[fdi].flags): + clearKey(s, fdi) + s.pollRemove(fdi.cint) + + proc newEvent*(): SelectEvent = + var fds: array[2, cint] + if posix.pipe(fds) == -1: + raiseOSError(osLastError()) + setNonBlocking(fds[0]) + setNonBlocking(fds[1]) + result = cast[SelectEvent](allocShared0(sizeof(SelectEventImpl))) + result.rfd = fds[0] + result.wfd = fds[1] + + proc setEvent*(ev: SelectEvent) = + var data: int = 1 + if posix.write(ev.wfd, addr data, sizeof(int)) != sizeof(int): + raiseOSError(osLastError()) + + proc close*(ev: SelectEvent) = + discard posix.close(cint(ev.rfd)) + discard posix.close(cint(ev.wfd)) + deallocShared(cast[pointer](ev)) + + proc selectInto*[T](s: Selector[T], timeout: int, + results: var openarray[ReadyKey[T]]): int = + var maxResults = MAX_POLL_RESULT_EVENTS + if maxResults > len(results): + maxResults = len(results) + + s.withPollLock(): + var count = posix.poll(addr(s.pollfds[0]), Tnfds(s.pollcnt), timeout) + if count > 0: + var i = 0 + var k = 0 + var rindex = 0 + while (i < s.pollcnt) and (k < count) and (rindex < maxResults): + let revents = s.pollfds[i].revents + let fd = s.pollfds[i].fd + if revents != 0: + var events: set[Event] = {} + if (revents and POLLIN) != 0: + events = events + {Event.Read} + if (revents and POLLOUT) != 0: + events = events + {Event.Write} + if (revents and POLLERR) != 0 or (revents and POLLHUP) != 0 or + (revents and POLLNVAL) != 0: + events = events + {Event.Error} + + var skey = addr(s.fds[fd]) + if Event.flagUser in skey.flags: + if Event.Read in events: + var data: int = 0 + if posix.read(fd, addr data, sizeof(int)) != sizeof(int): + let err = osLastError() + if err != OSErrorCode(EAGAIN): + raiseOSError(osLastError()) + else: + # someone already consumed event data + inc(i) + continue + events = {Event.User} + + results[rindex].fd = fd + results[rindex].events = events + results[rindex].data = skey.key.data + s.pollfds[i].revents = 0 + inc(rindex) + inc(k) + inc(i) + result = k + elif count == 0: + discard + else: + let err = osLastError() + if err.cint == EINTR: + discard + else: + raiseOSError(osLastError()) + + proc select*[T](s: Selector[T], timeout: int): seq[ReadyKey[T]] = + result = newSeq[ReadyKey[T]](MAX_POLL_RESULT_EVENTS) + var count = selectInto(s, timeout, result) + result.setLen(count) + + when not defined(windows): + template withData*[T](s: Selector[T], fd: SocketHandle, value, + body: untyped) = + var fdi = int(fd) + s.checkMaxFd(fdi) + if s.fds[fdi].ident != 0: + var value = addr(s.fds[fdi].key.data) + body + + template withData*[T](s: Selector[T], fd: SocketHandle, value, body1, + body2: untyped) = + var fdi = int(fd) + s.checkMaxFd(fdi) + if s.fds[fdi].ident != 0: + var value = addr(s.fds[fdi].key.data) + body1 + else: + body2 + else: + template withData*(s: Selector, fd: SocketHandle, value, body: untyped) = + s.fds.withValue(fd, skey) do: + var value {.inject.} = addr(skey.key.data) + body + + template withData*(s: Selector, fd: SocketHandle, value, + body1, body2: untyped) = + s.fds.withValue(fd, skey) do: + var value {.inject.} = addr(skey.key.data) + body1 + do: + body2 diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b6edb834c..7d1b1a3d9 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -160,7 +160,8 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isSpace() and result + if not c.isSpace(): + return false proc isLower*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsLowerStr".}= @@ -326,6 +327,20 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} = + ## Checks if `s` is nil or empty. + result = len(s) == 0 + +proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = + ## Checks if `s` is nil or consists entirely of whitespace characters. + if len(s) == 0: + return true + + result = true + for c in s: + if not c.isSpace(): + return false + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. @@ -2156,6 +2171,17 @@ when isMainModule: doAssert isSpace(" ") doAssert(not isSpace("ABc \td")) + doAssert(isNilOrEmpty("")) + doAssert(isNilOrEmpty(nil)) + doAssert(not isNilOrEmpty("test")) + doAssert(not isNilOrEmpty(" ")) + + doAssert(isNilOrWhitespace("")) + doAssert(isNilOrWhitespace(nil)) + doAssert(isNilOrWhitespace(" ")) + doAssert(isNilOrWhitespace("\t\l \v\r\f")) + doAssert(not isNilOrWhitespace("ABc \td")) + doAssert isLower('a') doAssert isLower('z') doAssert(not isLower('A')) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 60f064e7c..c135f6a00 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -493,17 +493,21 @@ template styledEcho*(args: varargs[expr]): expr = ## Echoes styles arguments to stdout using ``styledWriteLine``. callStyledEcho(args) -when defined(nimdoc): - proc getch*(): char = - ## Read a single character from the terminal, blocking until it is entered. - ## The character is not printed to the terminal. This is not available for - ## Windows. - discard -elif not defined(windows): - proc getch*(): char = - ## Read a single character from the terminal, blocking until it is entered. - ## The character is not printed to the terminal. This is not available for - ## Windows. +proc getch*(): char = + ## Read a single character from the terminal, blocking until it is entered. + ## The character is not printed to the terminal. + when defined(windows): + let fd = getStdHandle(STD_INPUT_HANDLE) + var keyEvent = KEY_EVENT_RECORD() + var numRead: cint + while true: + # Block until character is entered + doAssert(waitForSingleObject(fd, INFINITE) == WAIT_OBJECT_0) + doAssert(readConsoleInput(fd, addr(keyEvent), 1, addr(numRead)) != 0) + if numRead == 0 or keyEvent.eventType != 1 or keyEvent.bKeyDown == 0: + continue + return char(keyEvent.uChar) + else: let fd = getFileHandle(stdin) var oldMode: Termios discard fd.tcgetattr(addr oldMode) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 04edeb2cb..c9b4b610c 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -1016,4 +1016,23 @@ proc wsaCloseEvent*(hEvent: Handle): bool {.stdcall, importc: "WSACloseEvent", dynlib: "ws2_32.dll".} proc wsaResetEvent*(hEvent: Handle): bool - {.stdcall, importc: "WSAResetEvent", dynlib: "ws2_32.dll".} \ No newline at end of file + {.stdcall, importc: "WSAResetEvent", dynlib: "ws2_32.dll".} + +type + KEY_EVENT_RECORD* {.final, pure.} = object + eventType*: int16 + bKeyDown*: WINBOOL + wRepeatCount*: int16 + wVirtualKeyCode*: int16 + wVirtualScanCode*: int16 + uChar*: int16 + dwControlKeyState*: DWORD + +when defined(useWinAnsi): + proc readConsoleInput*(hConsoleInput: Handle, lpBuffer: pointer, nLength: cint, + lpNumberOfEventsRead: ptr cint): cint + {.stdcall, dynlib: "kernel32", importc: "ReadConsoleInputA".} +else: + proc readConsoleInput*(hConsoleInput: Handle, lpBuffer: pointer, nLength: cint, + lpNumberOfEventsRead: ptr cint): cint + {.stdcall, dynlib: "kernel32", importc: "ReadConsoleInputW".} \ No newline at end of file diff --git a/tests/async/tioselectors.nim b/tests/async/tioselectors.nim new file mode 100644 index 000000000..ebfe10fd6 --- /dev/null +++ b/tests/async/tioselectors.nim @@ -0,0 +1,407 @@ +discard """ + file: "tioselectors.nim" + output: "All tests passed!" +""" +import ioselectors + +const hasThreadSupport = compileOption("threads") + +template processTest(t, x: untyped) = + #stdout.write(t) + #stdout.flushFile() + if not x: echo(t & " FAILED\r\n") + +when not defined(windows): + import os, posix, osproc, nativesockets, times + + const supportedPlatform = defined(macosx) or defined(freebsd) or + defined(netbsd) or defined(openbsd) or + defined(linux) + + proc socket_notification_test(): bool = + proc create_test_socket(): SocketHandle = + var sock = posix.socket(posix.AF_INET, posix.SOCK_STREAM, + posix.IPPROTO_TCP) + var x: int = fcntl(sock, F_GETFL, 0) + if x == -1: raiseOSError(osLastError()) + else: + var mode = x or O_NONBLOCK + if fcntl(sock, F_SETFL, mode) == -1: + raiseOSError(osLastError()) + result = sock + + var client_message = "SERVER HELLO =>" + var server_message = "CLIENT HELLO" + var buffer : array[128, char] + + var selector = newSelector[int]() + var client_socket = create_test_socket() + var server_socket = create_test_socket() + + registerHandle(selector, server_socket, {Event.Read}, 0) + registerHandle(selector, client_socket, {Event.Write}, 0) + + var option : int32 = 1 + if setsockopt(server_socket, cint(SOL_SOCKET), cint(SO_REUSEADDR), + addr(option), sizeof(option).SockLen) < 0: + raiseOSError(osLastError()) + + var aiList = getAddrInfo("0.0.0.0", Port(13337)) + if bindAddr(server_socket, aiList.ai_addr, + aiList.ai_addrlen.Socklen) < 0'i32: + dealloc(aiList) + raiseOSError(osLastError()) + discard server_socket.listen() + dealloc(aiList) + + aiList = getAddrInfo("127.0.0.1", Port(13337)) + discard posix.connect(client_socket, aiList.ai_addr, + aiList.ai_addrlen.Socklen) + dealloc(aiList) + var rc1 = selector.select(100) + assert(len(rc1) == 2) + + var sockAddress: SockAddr + var addrLen = sizeof(sockAddress).Socklen + var server2_socket = accept(server_socket, + cast[ptr SockAddr](addr(sockAddress)), + addr(addrLen)) + assert(server2_socket != osInvalidSocket) + selector.registerHandle(server2_socket, {Event.Read}, 0) + + if posix.send(client_socket, addr(client_message[0]), + len(client_message), 0) == -1: + raiseOSError(osLastError()) + + selector.updateHandle(client_socket, {Event.Read}) + + var rc2 = selector.select(100) + assert(len(rc2) == 1) + + var read_count = posix.recv(server2_socket, addr (buffer[0]), 128, 0) + if read_count == -1: + raiseOSError(osLastError()) + + assert(read_count == len(client_message)) + var test1 = true + for i in 0..<read_count: + if client_message[i] != buffer[i]: + test1 = false + break + assert(test1) + + selector.updateHandle(server2_socket, {Event.Write}) + var rc3 = selector.select(0) + assert(len(rc3) == 1) + if posix.send(server2_socket, addr(server_message[0]), + len(server_message), 0) == -1: + raiseOSError(osLastError()) + selector.updateHandle(server2_socket, {Event.Read}) + + var rc4 = selector.select(100) + assert(len(rc4) == 1) + read_count = posix.recv(client_socket, addr(buffer[0]), 128, 0) + if read_count == -1: + raiseOSError(osLastError()) + + assert(read_count == len(server_message)) + var test2 = true + for i in 0..<read_count: + if server_message[i] != buffer[i]: + test2 = false + break + assert(test2) + + selector.unregister(server_socket) + selector.unregister(server2_socket) + selector.unregister(client_socket) + discard posix.close(server_socket) + discard posix.close(server2_socket) + discard posix.close(client_socket) + assert(selector.isEmpty()) + close(selector) + result = true + + proc event_notification_test(): bool = + var selector = newSelector[int]() + var event = newEvent() + selector.registerEvent(event, 1) + selector.flush() + event.setEvent() + var rc1 = selector.select(0) + event.setEvent() + var rc2 = selector.select(0) + var rc3 = selector.select(0) + assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0) + var ev1 = rc1[0].data + var ev2 = rc2[0].data + assert(ev1 == 1 and ev2 == 1) + selector.unregister(event) + event.close() + assert(selector.isEmpty()) + selector.close() + result = true + + when supportedPlatform: + proc timer_notification_test(): bool = + var selector = newSelector[int]() + var timer = selector.registerTimer(100, false, 0) + var rc1 = selector.select(140) + var rc2 = selector.select(140) + assert(len(rc1) == 1 and len(rc2) == 1) + selector.unregister(timer) + selector.flush() + selector.registerTimer(100, true, 0) + var rc3 = selector.select(120) + var rc4 = selector.select(120) + assert(len(rc3) == 1 and len(rc4) == 0) + assert(selector.isEmpty()) + selector.close() + result = true + + proc process_notification_test(): bool = + var selector = newSelector[int]() + var process2 = startProcess("/bin/sleep", "", ["2"], nil, + {poStdErrToStdOut, poUsePath}) + discard startProcess("/bin/sleep", "", ["1"], nil, + {poStdErrToStdOut, poUsePath}) + + selector.registerProcess(process2.processID, 0) + var rc1 = selector.select(1500) + var rc2 = selector.select(1500) + var r = len(rc1) + len(rc2) + assert(r == 1) + result = true + + proc signal_notification_test(): bool = + var sigset1n, sigset1o, sigset2n, sigset2o: Sigset + var pid = posix.getpid() + + discard sigemptyset(sigset1n) + discard sigemptyset(sigset1o) + discard sigemptyset(sigset2n) + discard sigemptyset(sigset2o) + + when hasThreadSupport: + if pthread_sigmask(SIG_BLOCK, sigset1n, sigset1o) == -1: + raiseOSError(osLastError()) + else: + if sigprocmask(SIG_BLOCK, sigset1n, sigset1o) == -1: + raiseOSError(osLastError()) + + var selector = newSelector[int]() + var s1 = selector.registerSignal(SIGUSR1, 1) + var s2 = selector.registerSignal(SIGUSR2, 2) + var s3 = selector.registerSignal(SIGTERM, 3) + selector.flush() + + discard posix.kill(pid, SIGUSR1) + discard posix.kill(pid, SIGUSR2) + discard posix.kill(pid, SIGTERM) + var rc = selector.select(0) + selector.unregister(s1) + selector.unregister(s2) + selector.unregister(s3) + + when hasThreadSupport: + if pthread_sigmask(SIG_BLOCK, sigset2n, sigset2o) == -1: + raiseOSError(osLastError()) + else: + if sigprocmask(SIG_BLOCK, sigset2n, sigset2o) == -1: + raiseOSError(osLastError()) + + assert(len(rc) == 3) + assert(rc[0].data + rc[1].data + rc[2].data == 6) # 1 + 2 + 3 + assert(equalMem(addr sigset1o, addr sigset2o, sizeof(Sigset))) + assert(selector.isEmpty()) + result = true + + when hasThreadSupport: + + var counter = 0 + + proc event_wait_thread(event: SelectEvent) {.thread.} = + var selector = newSelector[int]() + selector.registerEvent(event, 1) + selector.flush() + var rc = selector.select(1000) + if len(rc) == 1: + inc(counter) + selector.unregister(event) + assert(selector.isEmpty()) + + proc mt_event_test(): bool = + var + thr: array [0..7, Thread[SelectEvent]] + var selector = newSelector[int]() + var sock = newNativeSocket() + var event = newEvent() + for i in 0..high(thr): + createThread(thr[i], event_wait_thread, event) + selector.registerHandle(sock, {Event.Read}, 1) + discard selector.select(500) + selector.unregister(sock) + event.setEvent() + joinThreads(thr) + assert(counter == 1) + result = true + + processTest("Socket notification test...", socket_notification_test()) + processTest("User event notification test...", event_notification_test()) + when hasThreadSupport: + processTest("Multithreaded user event notification test...", + mt_event_test()) + when supportedPlatform: + processTest("Timer notification test...", timer_notification_test()) + processTest("Process notification test...", process_notification_test()) + processTest("Signal notification test...", signal_notification_test()) + echo("All tests passed!") +else: + import nativesockets, winlean, os, osproc + + proc socket_notification_test(): bool = + proc create_test_socket(): SocketHandle = + var sock = newNativeSocket() + setBlocking(sock, false) + result = sock + + var client_message = "SERVER HELLO =>" + var server_message = "CLIENT HELLO" + var buffer : array[128, char] + + var selector = newSelector[int]() + var client_socket = create_test_socket() + var server_socket = create_test_socket() + + selector.registerHandle(server_socket, {Event.Read}, 0) + selector.registerHandle(client_socket, {Event.Write}, 0) + + var option : int32 = 1 + if setsockopt(server_socket, cint(SOL_SOCKET), cint(SO_REUSEADDR), + addr(option), sizeof(option).SockLen) < 0: + raiseOSError(osLastError()) + + var aiList = getAddrInfo("0.0.0.0", Port(13337)) + if bindAddr(server_socket, aiList.ai_addr, + aiList.ai_addrlen.Socklen) < 0'i32: + dealloc(aiList) + raiseOSError(osLastError()) + discard server_socket.listen() + dealloc(aiList) + + aiList = getAddrInfo("127.0.0.1", Port(13337)) + discard connect(client_socket, aiList.ai_addr, + aiList.ai_addrlen.Socklen) + dealloc(aiList) + # for some reason Windows select doesn't return both + # descriptors from first call, so we need to make 2 calls + discard selector.select(100) + var rcm = selector.select(100) + assert(len(rcm) == 2) + + var sockAddress = SockAddr() + var addrLen = sizeof(sockAddress).Socklen + var server2_socket = accept(server_socket, + cast[ptr SockAddr](addr(sockAddress)), + addr(addrLen)) + assert(server2_socket != osInvalidSocket) + selector.registerHandle(server2_socket, {Event.Read}, 0) + + if send(client_socket, cast[pointer](addr(client_message[0])), + cint(len(client_message)), 0) == -1: + raiseOSError(osLastError()) + + selector.updateHandle(client_socket, {Event.Read}) + + var rc2 = selector.select(100) + assert(len(rc2) == 1) + + var read_count = recv(server2_socket, addr (buffer[0]), 128, 0) + if read_count == -1: + raiseOSError(osLastError()) + + assert(read_count == len(client_message)) + var test1 = true + for i in 0..<read_count: + if client_message[i] != buffer[i]: + test1 = false + break + assert(test1) + + if send(server2_socket, cast[pointer](addr(server_message[0])), + cint(len(server_message)), 0) == -1: + raiseOSError(osLastError()) + + var rc3 = selector.select(0) + assert(len(rc3) == 1) + read_count = recv(client_socket, addr(buffer[0]), 128, 0) + if read_count == -1: + raiseOSError(osLastError()) + + assert(read_count == len(server_message)) + var test2 = true + for i in 0..<read_count: + if server_message[i] != buffer[i]: + test2 = false + break + assert(test2) + + selector.unregister(server_socket) + selector.unregister(server2_socket) + selector.unregister(client_socket) + close(server_socket) + close(server2_socket) + close(client_socket) + assert(selector.isEmpty()) + close(selector) + result = true + + proc event_notification_test(): bool = + var selector = newSelector[int]() + var event = newEvent() + selector.registerEvent(event, 1) + selector.flush() + event.setEvent() + var rc1 = selector.select(0) + event.setEvent() + var rc2 = selector.select(0) + var rc3 = selector.select(0) + assert(len(rc1) == 1 and len(rc2) == 1 and len(rc3) == 0) + var ev1 = rc1[0].data + var ev2 = rc2[0].data + assert(ev1 == 1 and ev2 == 1) + selector.unregister(event) + event.close() + assert(selector.isEmpty()) + selector.close() + result = true + + when hasThreadSupport: + var counter = 0 + + proc event_wait_thread(event: SelectEvent) {.thread.} = + var selector = newSelector[int]() + selector.registerEvent(event, 1) + selector.flush() + var rc = selector.select(500) + if len(rc) == 1: + inc(counter) + selector.unregister(event) + assert(selector.isEmpty()) + + proc mt_event_test(): bool = + var thr: array [0..7, Thread[SelectEvent]] + var event = newEvent() + for i in 0..high(thr): + createThread(thr[i], event_wait_thread, event) + event.setEvent() + joinThreads(thr) + assert(counter == 1) + result = true + + processTest("Socket notification test...", socket_notification_test()) + processTest("User event notification test...", event_notification_test()) + when hasThreadSupport: + processTest("Multithreaded user event notification test...", + mt_event_test()) + echo("All tests passed!") diff --git a/tests/async/tioselectors.nim.cfg b/tests/async/tioselectors.nim.cfg new file mode 100644 index 000000000..b1b896858 --- /dev/null +++ b/tests/async/tioselectors.nim.cfg @@ -0,0 +1 @@ +threads:on -d:threadsafe diff --git a/tests/generics/t88.nim b/tests/generics/t88.nim new file mode 100644 index 000000000..93d93f063 --- /dev/null +++ b/tests/generics/t88.nim @@ -0,0 +1,25 @@ +# Issue 88 + +type + BaseClass[V] = object of RootObj + b: V + +proc new[V](t: typedesc[BaseClass], v: V): BaseClass[V] = + BaseClass[V](b: v) + +proc baseMethod[V](v: BaseClass[V]): V = v.b +proc overridedMethod[V](v: BaseClass[V]): V = v.baseMethod + +type + ChildClass[V] = object of BaseClass[V] + c: V + +proc new[V](t: typedesc[ChildClass], v1, v2: V): ChildClass[V] = + ChildClass[V](b: v1, c: v2) + +proc overridedMethod[V](v: ChildClass[V]): V = v.c + +let c = ChildClass[string].new("Base", "Child") + +assert c.baseMethod == "Base" +assert c.overridedMethod == "Child" diff --git a/tools/website.tmpl b/tools/website.tmpl index 6bdbd9b31..cf2c72a60 100644 --- a/tools/website.tmpl +++ b/tools/website.tmpl @@ -224,6 +224,7 @@ runForever() <p>This page lists the companies and individuals that are, very kindly, contributing a monthly amount to help sustain Nim's development. For more details take a look at the <a href="https://salt.bountysource.com/teams/nim">Bountysource campaign</a>.</p> +<p class="lastUpdate">Last updated: ${getTime().getGMTime().format("dd/MM/yyyy")}</p> <dl> #for sponsor in sponsors: <dt class="level-${sponsor.level}"> diff --git a/web/assets/bountysource/xored.png b/web/assets/bountysource/xored.png new file mode 100644 index 000000000..7a07cdd31 --- /dev/null +++ b/web/assets/bountysource/xored.png Binary files differdiff --git a/web/assets/style.css b/web/assets/style.css index d63177469..af32fad07 100644 --- a/web/assets/style.css +++ b/web/assets/style.css @@ -606,6 +606,11 @@ dt.level-1 { color: #2f2f2f !important; } +p.lastUpdate { + font-size: 12pt; + color: #6d6d6d; +} + /* News articles */ .metadata { @@ -614,3 +619,4 @@ dt.level-1 { margin-top: -16pt; color: #4f4f4f; } + diff --git a/web/news.rst b/web/news.rst index cd605b760..26f3adfdf 100644 --- a/web/news.rst +++ b/web/news.rst @@ -2,6 +2,9 @@ News ==== +`2016-06-23 Launching the 2016 Nim community survey <news/2016_06_23_launching_the_2016_nim_community_survey.html>`_ +=================================== + `2016-06-11 Version 0.14.2 released <news/2016_06_11_version_0_14_2_released.html>`_ =================================== diff --git a/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst b/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst new file mode 100644 index 000000000..0b87577aa --- /dev/null +++ b/web/news/2016_06_23_launching_the_2016_nim_community_survey.rst @@ -0,0 +1,29 @@ +Launching the 2016 Nim Community Survey +======================================= + +.. container:: metadata + + Posted by Dominik Picheta on 23/06/2016 + +We are proud to announce the official +`2016 Nim Community Survey <http://goo.gl/forms/XJ3TPsaiIQe5HlTB2>`_! No matter +whether you use Nim today, have used Nim previously, or never used Nim before; +we want to know your opinions. +Your feedback will help the Nim project understand its strengths and +weaknesses, and to determine development priorities for the future. + +It shouldn't take you much longer than 5 to 10 minutes to complete this survey. +Submissions will be accepted until around the 23rd of July, depending on the +response rates. If you have any questions or feedback, please don't hesitate +to get in touch with us via email at survey@nim-lang.org or on the +`Nim Forum <http://forum.nim-lang.org>`_. + +We would appreciate your help in spreading the word about this survey. Share +the above link on your social network feeds, with your colleagues and in +other communities. + +Thank you to everyone that helped develop and test the survey! Once the +submission period ends, the results will be shown here and publicised via +Twitter, the Nim Forum and IRC. + +Thanks for your time! diff --git a/web/sponsors.csv b/web/sponsors.csv index 046469d82..9c28f43ff 100644 --- a/web/sponsors.csv +++ b/web/sponsors.csv @@ -1,22 +1,31 @@ logo,name,url,this_month,all_time,since,level -assets/bountysource/secondspectrum.png,Second Spectrum,http://www.secondspectrum.com/,250,250,"May 5, 2016",250 +assets/bountysource/secondspectrum.png,Second Spectrum,http://www.secondspectrum.com/,250,500,"May 5, 2016",250 +assets/bountysource/xored.png,Xored Software,http://www.xored.com/,250,250,"Jun 20, 2016",250 ,flyx,http://flyx.org,35,70,"Apr 7, 2016",25 -,Adrian Veith,https://github.com/AdrianV,25,50,"Apr 20, 2016",25 +,Adrian Veith,,25,50,"Apr 20, 2016",25 ,Federico Ceratto,http://firelet.net,25,50,"Apr 7, 2016",25 ,Lloyd Moore,https://github.com/iolloyd,25,50,"Apr 29, 2016",25 ,Ruslan Mustakov,https://github.com/endragor,25,50,"Apr 7, 2016",25 ,Yuriy Glukhov,https://github.com/yglukhov/,25,50,"Apr 6, 2016",25 +,Euan T.,http://euantorano.co.uk,25,25,"Jun 7, 2016",25 ,TedSinger,https://github.com/TedSinger,15,30,"Apr 9, 2016",10 ,Pradeep Gowda,https://www.btbytes.com/,10,20,"Apr 6, 2016",10 ,Jonathan Arnett,http://j3rn.com,10,10,"May 20, 2016",10 +,Matthew Baulch,,10,10,"Jun 7, 2016",10 ,niebaopeng,https://github.com/niebaopeng,10,10,"Apr 15, 2016",10 +,Oskari Timperi,,10,10,"Jun 8, 2016",10 +,RationalG,https://github.com/RationalG,10,10,"Jun 17, 2016",10 +,Sergey Avseyev,http://avsej.net,10,10,"Jun 10, 2016",10 +,Zach Aysan,http://venn.lc,10,10,"Jun 7, 2016",10 +,swalf,https://joindiaspora.com/people/889b5d8ff6ed0b98,5,30,"May 9, 2016",5 ,Handojo Goenadi,,5,10,"Apr 19, 2016",5 ,John Novak,http://www.johnnovak.net/,5,10,"Apr 29, 2016",5 +,Konstantin Molchanov,http://sloth-ci.com,5,10,"May 13, 2016",5 ,Matthew Newton,,5,10,"Apr 20, 2016",5 -,McSpiros,https://github.com/SpirosMakris,5,10,"Apr 6, 2016",5 ,Mirek Rusin,http://quartzcomposer.com,5,10,"Apr 9, 2016",5 -,pyloor,https://github.com/pyloor,5,10,"May 16, 2016",5 +,pyloor ,,5,10,"May 16, 2016",5 ,Sokolov Yura,https://github.com/funny-falcon,5,10,"Apr 7, 2016",5 +,calind,,5,5,"Jun 7, 2016",5 ,Ivan Florentin,https://github.com/ivanflorentin,5,5,"May 2, 2016",5 ,Michael D. Sklaroff,,1,2,"Apr 27, 2016",1 ,Svend Knudsen,,1,2,"Apr 11, 2016",1 diff --git a/web/ticker.html b/web/ticker.html index 9e88456a9..e1232d107 100644 --- a/web/ticker.html +++ b/web/ticker.html @@ -1,3 +1,8 @@ +<a class="news" href="$1news/2016_06_23_launching_the_2016_nim_community_survey.html"> + <h4>June 23, 2016</h4> + <p>Launching the 2016 Nim community survey!</p> +</a> + <a class="news" href="$1news/2016_06_11_version_0_14_2_released.html"> <h4>June 11, 2016</h4> <p>Nim version 0.14.2 has been released!</p> @@ -18,9 +23,4 @@ <p>Nim version 0.13.0 has been released!</p> </a> -<a class="news" href="$1news/2016_01_18_andreas_rumpfs_talk_at_oscon_amsterdam.html"> - <h4>January 18, 2016</h4> - <p>Andreas Rumpf's talk at OSCON Amsterdam</p> -</a> - <a href="$1news.html" class="blue">See All News...</a> |