auto merge of #10965 : alexcrichton/rust/libgreen, r=brson
This pull request extracts all scheduling functionality from libstd, moving it into its own separate crates. The new libnative and libgreen will be the new way in which 1:1 and M:N scheduling is implemented. The standard library still requires an interface to the runtime, however, (think of things like `std::comm` and `io::println`). The interface is now defined by the `Runtime` trait inside of `std::rt`. The booting process is now that libgreen defines the start lang-item and that's it. I want to extend this soon to have libnative also have a "start lang item" but also allow libgreen and libnative to be linked together in the same process. For now though, only libgreen can be used to start a program (unless you define the start lang item yourself). Again though, I want to change this soon, I just figured that this pull request is large enough as-is. This certainly wasn't a smooth transition, certain functionality has no equivalent in this new separation, and some functionality is now better enabled through this new system. I did my best to separate all of the commits by topic and keep things fairly bite-sized, although are indeed larger than others. As a note, this is currently rebased on top of my `std::comm` rewrite (or at least an old copy of it), but none of those commits need reviewing (that will all happen in another pull request).
This commit is contained in:
commit
9477c49a7b
50
Makefile.in
50
Makefile.in
|
@ -235,6 +235,8 @@ CFG_LIBSYNTAX_$(1) :=$(call CFG_LIB_NAME_$(1),syntax)
|
|||
CFG_LIBRUSTPKG_$(1) :=$(call CFG_LIB_NAME_$(1),rustpkg)
|
||||
CFG_LIBRUSTDOC_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc)
|
||||
CFG_LIBRUSTUV_$(1) :=$(call CFG_LIB_NAME_$(1),rustuv)
|
||||
CFG_LIBGREEN_$(1) :=$(call CFG_LIB_NAME_$(1),green)
|
||||
CFG_LIBNATIVE_$(1) :=$(call CFG_LIB_NAME_$(1),native)
|
||||
|
||||
EXTRALIB_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),extra)
|
||||
STDLIB_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),std)
|
||||
|
@ -243,6 +245,8 @@ LIBSYNTAX_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),syntax)
|
|||
LIBRUSTPKG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustpkg)
|
||||
LIBRUSTDOC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc)
|
||||
LIBRUSTUV_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustuv)
|
||||
LIBGREEN_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),green)
|
||||
LIBNATIVE_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),native)
|
||||
EXTRALIB_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),extra)
|
||||
STDLIB_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),std)
|
||||
LIBRUSTC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustc)
|
||||
|
@ -250,12 +254,16 @@ LIBSYNTAX_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),syntax)
|
|||
LIBRUSTPKG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustpkg)
|
||||
LIBRUSTDOC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc)
|
||||
LIBRUSTUV_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustuv)
|
||||
LIBGREEN_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),green)
|
||||
LIBNATIVE_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),native)
|
||||
|
||||
EXTRALIB_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,extra)
|
||||
STDLIB_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,std)
|
||||
LIBRUSTUV_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,rustuv)
|
||||
LIBSYNTAX_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,syntax)
|
||||
LIBRUSTC_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,rustc)
|
||||
LIBNATIVE_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,native)
|
||||
LIBGREEN_RGLOB_$(1) :=$(call CFG_RLIB_GLOB,green)
|
||||
|
||||
endef
|
||||
|
||||
|
@ -272,9 +280,15 @@ define CHECK_FOR_OLD_GLOB_MATCHES_EXCEPT
|
|||
endef
|
||||
|
||||
# Same interface as above, but deletes rather than just listing the files.
|
||||
ifdef VERBOSE
|
||||
define REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT
|
||||
$(Q)MATCHES="$(filter-out %$(3),$(wildcard $(1)/$(2)))"; if [ -n "$$MATCHES" ] ; then echo "warning: removing previous" \'$(2)\' "libraries:" $$MATCHES; rm $$MATCHES ; fi
|
||||
endef
|
||||
else
|
||||
define REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT
|
||||
$(Q)MATCHES="$(filter-out %$(3),$(wildcard $(1)/$(2)))"; if [ -n "$$MATCHES" ] ; then rm $$MATCHES ; fi
|
||||
endef
|
||||
endif
|
||||
|
||||
# We use a different strategy for LIST_ALL_OLD_GLOB_MATCHES_EXCEPT
|
||||
# than in the macros above because it needs the result of running the
|
||||
|
@ -319,6 +333,22 @@ LIBRUSTUV_CRATE := $(S)src/librustuv/lib.rs
|
|||
LIBRUSTUV_INPUTS := $(wildcard $(addprefix $(S)src/librustuv/, \
|
||||
*.rs */*.rs))
|
||||
|
||||
######################################################################
|
||||
# Green threading library variables
|
||||
######################################################################
|
||||
|
||||
LIBGREEN_CRATE := $(S)src/libgreen/lib.rs
|
||||
LIBGREEN_INPUTS := $(wildcard $(addprefix $(S)src/libgreen/, \
|
||||
*.rs */*.rs))
|
||||
|
||||
######################################################################
|
||||
# Native threading library variables
|
||||
######################################################################
|
||||
|
||||
LIBNATIVE_CRATE := $(S)src/libnative/lib.rs
|
||||
LIBNATIVE_INPUTS := $(wildcard $(addprefix $(S)src/libnative/, \
|
||||
*.rs */*.rs))
|
||||
|
||||
######################################################################
|
||||
# rustc crate variables
|
||||
######################################################################
|
||||
|
@ -430,6 +460,16 @@ HLIBRUSTUV_DEFAULT$(1)_H_$(3) = \
|
|||
TLIBRUSTUV_DEFAULT$(1)_T_$(2)_H_$(3) = \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2))
|
||||
|
||||
HLIBGREEN_DEFAULT$(1)_H_$(3) = \
|
||||
$$(HLIB$(1)_H_$(3))/$(CFG_LIBGREEN_$(3))
|
||||
TLIBGREEN_DEFAULT$(1)_T_$(2)_H_$(3) = \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBGREEN_$(2))
|
||||
|
||||
HLIBNATIVE_DEFAULT$(1)_H_$(3) = \
|
||||
$$(HLIB$(1)_H_$(3))/$(CFG_LIBNATIVE_$(3))
|
||||
TLIBNATIVE_DEFAULT$(1)_T_$(2)_H_$(3) = \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBNATIVE_$(2))
|
||||
|
||||
# Preqrequisites for using the stageN compiler
|
||||
ifeq ($(1),0)
|
||||
HSREQ$(1)_H_$(3) = $$(HBIN$(1)_H_$(3))/rustc$$(X_$(3))
|
||||
|
@ -441,6 +481,8 @@ HSREQ$(1)_H_$(3) = \
|
|||
$$(HLIBSYNTAX_DEFAULT$(1)_H_$(3)) \
|
||||
$$(HLIBRUSTC_DEFAULT$(1)_H_$(3)) \
|
||||
$$(HLIBRUSTUV_DEFAULT$(1)_H_$(3)) \
|
||||
$$(HLIBGREEN_DEFAULT$(1)_H_$(3)) \
|
||||
$$(HLIBNATIVE_DEFAULT$(1)_H_$(3)) \
|
||||
$$(MKFILE_DEPS)
|
||||
endif
|
||||
|
||||
|
@ -455,7 +497,9 @@ SREQ$(1)_T_$(2)_H_$(3) = \
|
|||
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_EXTRALIB_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2))
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBGREEN_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBNATIVE_$(2))
|
||||
|
||||
# Prerequisites for a working stageN compiler and libraries, for a specific target
|
||||
CSREQ$(1)_T_$(2)_H_$(3) = \
|
||||
|
@ -470,7 +514,9 @@ CSREQ$(1)_T_$(2)_H_$(3) = \
|
|||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2))
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBGREEN_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBNATIVE_$(2))
|
||||
|
||||
ifeq ($(1),0)
|
||||
# Don't run the the stage0 compiler under valgrind - that ship has sailed
|
||||
|
|
12
mk/clean.mk
12
mk/clean.mk
|
@ -90,6 +90,8 @@ clean$(1)_H_$(2):
|
|||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_STDLIB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_EXTRALIB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTUV_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBNATIVE_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBGREEN_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTC_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBSYNTAX_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(STDLIB_GLOB_$(2))
|
||||
|
@ -98,6 +100,10 @@ clean$(1)_H_$(2):
|
|||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(EXTRALIB_RGLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTUV_GLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTUV_RGLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBNATIVE_GLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBNATIVE_RGLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBGREEN_GLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBGREEN_RGLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTC_GLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBSYNTAX_GLOB_$(2))
|
||||
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTPKG_GLOB_$(2))
|
||||
|
@ -124,6 +130,8 @@ clean$(1)_T_$(2)_H_$(3):
|
|||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_EXTRALIB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBGREEN_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBNATIVE_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBSYNTAX_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(STDLIB_GLOB_$(2))
|
||||
|
@ -132,6 +140,10 @@ clean$(1)_T_$(2)_H_$(3):
|
|||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(EXTRALIB_RGLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTUV_GLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTUV_RGLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBNATIVE_GLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBNATIVE_RGLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBGREEN_GLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBGREEN_RGLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTC_GLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTC_RGLOB_$(2))
|
||||
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBSYNTAX_GLOB_$(2))
|
||||
|
|
|
@ -35,6 +35,9 @@ PKG_FILES := \
|
|||
libextra \
|
||||
libstd \
|
||||
libsyntax \
|
||||
librustuv \
|
||||
libgreen \
|
||||
libnative \
|
||||
rt \
|
||||
librustdoc \
|
||||
rustllvm \
|
||||
|
|
48
mk/host.mk
48
mk/host.mk
|
@ -25,13 +25,7 @@ define CP_HOST_STAGE_N
|
|||
|
||||
$$(HBIN$(2)_H_$(4))/rustc$$(X_$(4)): \
|
||||
$$(TBIN$(1)_T_$(4)_H_$(3))/rustc$$(X_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTC_$(4)) \
|
||||
$$(HSTDLIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HEXTRALIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBRUSTUV_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBRUSTC_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBSYNTAX_DEFAULT$(2)_H_$(4)) \
|
||||
| $$(HBIN$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(Q)cp $$< $$@
|
||||
|
@ -39,10 +33,6 @@ $$(HBIN$(2)_H_$(4))/rustc$$(X_$(4)): \
|
|||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTC_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTC_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBSYNTAX_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
$$(HSTDLIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HEXTRALIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBRUSTUV_DEFAULT$(2)_H_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
|
||||
@$$(call E, cp: $$@)
|
||||
|
@ -55,10 +45,11 @@ $$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTC_$(4)): \
|
|||
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBSYNTAX_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBSYNTAX_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
$$(HSTDLIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HEXTRALIB_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBRUSTUV_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBGREEN_DEFAULT$(2)_H_$(4)) \
|
||||
$$(HLIBNATIVE_DEFAULT$(2)_H_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBSYNTAX_GLOB_$(4)),$$(notdir $$@))
|
||||
|
@ -76,7 +67,6 @@ $$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)): \
|
|||
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_STDLIB_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(STDLIB_GLOB_$(4)),$$(notdir $$@))
|
||||
|
@ -98,8 +88,7 @@ $$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)): \
|
|||
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_EXTRALIB_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_EXTRALIB_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
$$(HSTDLIB_DEFAULT$(2)_H_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(EXTRALIB_GLOB_$(4)),$$(notdir $$@))
|
||||
|
@ -115,7 +104,6 @@ $$(HLIB$(2)_H_$(4))/$(CFG_EXTRALIB_$(4)): \
|
|||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTUV_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTUV_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_RUNTIME_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTUV_GLOB_$(4)),$$(notdir $$@))
|
||||
|
@ -128,6 +116,36 @@ $$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTUV_$(4)): \
|
|||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTUV_GLOB_$(4)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTUV_RGLOB_$(4)),$$(notdir $$@))
|
||||
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBGREEN_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBGREEN_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_GLOB_$(4)),$$(notdir $$@))
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_RGLOB_$(4)),$$(notdir $$@))
|
||||
$$(Q)cp $$< $$@
|
||||
$$(Q)cp -R $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBGREEN_GLOB_$(4)) \
|
||||
$$(wildcard $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBGREEN_RGLOB_$(4))) \
|
||||
$$(wildcard $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBGREEN_DSYM_GLOB_$(4))) \
|
||||
$$(HLIB$(2)_H_$(4))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_GLOB_$(4)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_RGLOB_$(4)),$$(notdir $$@))
|
||||
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_LIBNATIVE_$(4)): \
|
||||
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBNATIVE_$(4)) \
|
||||
$$(HLIB$(2)_H_$(4))/$(CFG_STDLIB_$(4)) \
|
||||
| $$(HLIB$(2)_H_$(4))/
|
||||
@$$(call E, cp: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_GLOB_$(4)),$$(notdir $$@))
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_RGLOB_$(4)),$$(notdir $$@))
|
||||
$$(Q)cp $$< $$@
|
||||
$$(Q)cp -R $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBNATIVE_GLOB_$(4)) \
|
||||
$$(wildcard $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBNATIVE_RGLOB_$(4))) \
|
||||
$$(wildcard $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBNATIVE_DSYM_GLOB_$(4))) \
|
||||
$$(HLIB$(2)_H_$(4))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_GLOB_$(4)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_RGLOB_$(4)),$$(notdir $$@))
|
||||
|
||||
$$(HBIN$(2)_H_$(4))/:
|
||||
mkdir -p $$@
|
||||
|
||||
|
|
|
@ -94,6 +94,10 @@ install-target-$(1)-host-$(2): $$(TSREQ$$(ISTAGE)_T_$(1)_H_$(2)) $$(SREQ$$(ISTAG
|
|||
$$(Q)$$(call INSTALL_LIB,$$(EXTRALIB_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTUV_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTUV_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBGREEN_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBGREEN_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBNATIVE_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBNATIVE_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,libmorestack.a)
|
||||
|
||||
endef
|
||||
|
@ -109,6 +113,10 @@ install-target-$(1)-host-$(2): $$(CSREQ$$(ISTAGE)_T_$(1)_H_$(2))
|
|||
$$(Q)$$(call INSTALL_LIB,$$(EXTRALIB_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTUV_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTUV_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBGREEN_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBGREEN_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBNATIVE_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBNATIVE_RGLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTC_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBSYNTAX_GLOB_$(1)))
|
||||
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTPKG_GLOB_$(1)))
|
||||
|
@ -149,6 +157,7 @@ install-host: $(CSREQ$(ISTAGE)_T_$(CFG_BUILD_)_H_$(CFG_BUILD_))
|
|||
$(Q)$(call INSTALL_LIB,$(STDLIB_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(EXTRALIB_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(LIBRUSTUV_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(LIBGREEN_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(LIBRUSTC_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(LIBSYNTAX_GLOB_$(CFG_BUILD)))
|
||||
$(Q)$(call INSTALL_LIB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD)))
|
||||
|
@ -174,6 +183,10 @@ uninstall:
|
|||
$(call HOST_LIB_FROM_HL_GLOB,$(EXTRALIB_RGLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTUV_GLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTUV_RGLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBGREEN_GLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBGREEN_RGLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBNATIVE_GLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBNATIVE_RGLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTC_GLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBSYNTAX_GLOB_$(CFG_BUILD))) \
|
||||
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD))) \
|
||||
|
@ -237,6 +250,7 @@ install-runtime-target-$(1)-host-$(2): $$(TSREQ$$(ISTAGE)_T_$(1)_H_$(2)) $$(SREQ
|
|||
$(Q)$(call ADB_PUSH,$$(TL$(1)$(2))/$$(STDLIB_GLOB_$(1)),$(CFG_RUNTIME_PUSH_DIR))
|
||||
$(Q)$(call ADB_PUSH,$$(TL$(1)$(2))/$$(EXTRALIB_GLOB_$(1)),$(CFG_RUNTIME_PUSH_DIR))
|
||||
$(Q)$(call ADB_PUSH,$$(TL$(1)$(2))/$$(LIBRUSTUV_GLOB_$(1)),$(CFG_RUNTIME_PUSH_DIR))
|
||||
$(Q)$(call ADB_PUSH,$$(TL$(1)$(2))/$$(LIBGREEN_GLOB_$(1)),$(CFG_RUNTIME_PUSH_DIR))
|
||||
endef
|
||||
|
||||
define INSTALL_RUNTIME_TARGET_CLEANUP_N
|
||||
|
@ -245,6 +259,7 @@ install-runtime-target-$(1)-cleanup:
|
|||
$(Q)$(call ADB_SHELL,rm,$(CFG_RUNTIME_PUSH_DIR)/$(STDLIB_GLOB_$(1)))
|
||||
$(Q)$(call ADB_SHELL,rm,$(CFG_RUNTIME_PUSH_DIR)/$(EXTRALIB_GLOB_$(1)))
|
||||
$(Q)$(call ADB_SHELL,rm,$(CFG_RUNTIME_PUSH_DIR)/$(LIBRUSTUV_GLOB_$(1)))
|
||||
$(Q)$(call ADB_SHELL,rm,$(CFG_RUNTIME_PUSH_DIR)/$(LIBGREEN_GLOB_$(1)))
|
||||
endef
|
||||
|
||||
$(eval $(call INSTALL_RUNTIME_TARGET_N,arm-linux-androideabi,$(CFG_BUILD)))
|
||||
|
|
34
mk/target.mk
34
mk/target.mk
|
@ -94,12 +94,37 @@ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTUV_$(2)): \
|
|||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTUV_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTUV_RGLOB_$(2)),$$(notdir $$@))
|
||||
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBGREEN_$(2)): \
|
||||
$$(LIBGREEN_CRATE) $$(LIBGREEN_INPUTS) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \
|
||||
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
|
||||
| $$(TLIB$(1)_T_$(2)_H_$(3))/
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_RGLOB_$(2)),$$(notdir $$@))
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) $$(WFLAGS_ST$(1)) \
|
||||
--out-dir $$(@D) $$< && touch $$@
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBGREEN_RGLOB_$(2)),$$(notdir $$@))
|
||||
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBNATIVE_$(2)): \
|
||||
$$(LIBNATIVE_CRATE) $$(LIBNATIVE_INPUTS) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \
|
||||
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
|
||||
| $$(TLIB$(1)_T_$(2)_H_$(3))/
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_RGLOB_$(2)),$$(notdir $$@))
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) $$(WFLAGS_ST$(1)) \
|
||||
--out-dir $$(@D) $$< && touch $$@
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBNATIVE_RGLOB_$(2)),$$(notdir $$@))
|
||||
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBSYNTAX_$(3)): \
|
||||
$$(LIBSYNTAX_CRATE) $$(LIBSYNTAX_INPUTS) \
|
||||
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TSTDLIB_DEFAULT$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TEXTRALIB_DEFAULT$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TLIBRUSTUV_DEFAULT$(1)_T_$(2)_H_$(3)) \
|
||||
| $$(TLIB$(1)_T_$(2)_H_$(3))/
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBSYNTAX_GLOB_$(2)),$$(notdir $$@))
|
||||
|
@ -135,16 +160,13 @@ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(3)): \
|
|||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTC_GLOB_$(2)),$$(notdir $$@))
|
||||
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTC_RGLOB_$(2)),$$(notdir $$@))
|
||||
|
||||
# NOTE: after the next snapshot remove these '-L' flags
|
||||
$$(TBIN$(1)_T_$(2)_H_$(3))/rustc$$(X_$(3)): \
|
||||
$$(DRIVER_CRATE) \
|
||||
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
|
||||
$$(SREQ$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(3)) \
|
||||
| $$(TBIN$(1)_T_$(2)_H_$(3))/
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) --cfg rustc -o $$@ $$< \
|
||||
-L $$(UV_SUPPORT_DIR_$(2)) \
|
||||
-L $$(dir $$(LIBUV_LIB_$(2)))
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) --cfg rustc -o $$@ $$<
|
||||
ifdef CFG_ENABLE_PAX_FLAGS
|
||||
@$$(call E, apply PaX flags: $$@)
|
||||
@"$(CFG_PAXCTL)" -cm "$$@"
|
||||
|
|
21
mk/tests.mk
21
mk/tests.mk
|
@ -14,7 +14,7 @@
|
|||
######################################################################
|
||||
|
||||
# The names of crates that must be tested
|
||||
TEST_TARGET_CRATES = std extra rustuv
|
||||
TEST_TARGET_CRATES = std extra rustuv green native
|
||||
TEST_DOC_CRATES = std extra
|
||||
TEST_HOST_CRATES = rustpkg rustc rustdoc syntax
|
||||
TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
|
||||
|
@ -162,6 +162,8 @@ $(info check: android device test dir $(CFG_ADB_TEST_DIR) ready \
|
|||
$(CFG_ADB_TEST_DIR)) \
|
||||
$(shell adb push $(TLIB2_T_arm-linux-androideabi_H_$(CFG_BUILD))/$(LIBRUSTUV_GLOB_arm-linux-androideabi) \
|
||||
$(CFG_ADB_TEST_DIR)) \
|
||||
$(shell adb push $(TLIB2_T_arm-linux-androideabi_H_$(CFG_BUILD))/$(LIBGREEN_GLOB_arm-linux-androideabi) \
|
||||
$(CFG_ADB_TEST_DIR)) \
|
||||
)
|
||||
else
|
||||
CFG_ADB_TEST_DIR=
|
||||
|
@ -187,7 +189,7 @@ check-test: cleantestlibs cleantmptestlogs all check-stage2-rfail
|
|||
|
||||
check-lite: cleantestlibs cleantmptestlogs \
|
||||
check-stage2-std check-stage2-extra check-stage2-rpass \
|
||||
check-stage2-rustuv \
|
||||
check-stage2-rustuv check-stage2-native check-stage2-green \
|
||||
check-stage2-rustpkg \
|
||||
check-stage2-rfail check-stage2-cfail check-stage2-rmake
|
||||
$(Q)$(CFG_PYTHON) $(S)src/etc/check-summary.py tmp/*.log
|
||||
|
@ -339,7 +341,8 @@ define TEST_RUNNER
|
|||
ifeq ($(NO_REBUILD),)
|
||||
STDTESTDEP_$(1)_$(2)_$(3) = $$(SREQ$(1)_T_$(2)_H_$(3)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_EXTRALIB_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBRUSTUV_$(2))
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBRUSTUV_$(2)) \
|
||||
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBGREEN_$(2))
|
||||
else
|
||||
STDTESTDEP_$(1)_$(2)_$(3) =
|
||||
endif
|
||||
|
@ -364,6 +367,18 @@ $(3)/stage$(1)/test/rustuvtest-$(2)$$(X_$(2)): \
|
|||
-L $$(UV_SUPPORT_DIR_$(2)) \
|
||||
-L $$(dir $$(LIBUV_LIB_$(2)))
|
||||
|
||||
$(3)/stage$(1)/test/nativetest-$(2)$$(X_$(2)): \
|
||||
$$(LIBNATIVE_CRATE) $$(LIBNATIVE_INPUTS) \
|
||||
$$(STDTESTDEP_$(1)_$(2)_$(3))
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< --test
|
||||
|
||||
$(3)/stage$(1)/test/greentest-$(2)$$(X_$(2)): \
|
||||
$$(LIBGREEN_CRATE) $$(LIBGREEN_INPUTS) \
|
||||
$$(STDTESTDEP_$(1)_$(2)_$(3))
|
||||
@$$(call E, compile_and_link: $$@)
|
||||
$$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< --test
|
||||
|
||||
$(3)/stage$(1)/test/syntaxtest-$(2)$$(X_$(2)): \
|
||||
$$(LIBSYNTAX_CRATE) $$(LIBSYNTAX_INPUTS) \
|
||||
$$(STDTESTDEP_$(1)_$(2)_$(3))
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
#[allow(non_camel_case_types)];
|
||||
#[deny(warnings)];
|
||||
|
||||
#[cfg(stage0)] extern mod green;
|
||||
extern mod extra;
|
||||
|
||||
use std::os;
|
||||
use std::rt;
|
||||
use std::io;
|
||||
use std::io::fs;
|
||||
|
||||
use extra::getopts;
|
||||
|
@ -234,7 +235,7 @@ pub fn run_tests(config: &config) {
|
|||
// sadly osx needs some file descriptor limits raised for running tests in
|
||||
// parallel (especially when we have lots and lots of child processes).
|
||||
// For context, see #8904
|
||||
rt::test::prepare_for_lots_of_tests();
|
||||
io::test::raise_fd_limit();
|
||||
let res = test::run_tests_console(&opts, tests);
|
||||
if !res { fail!("Some tests failed"); }
|
||||
}
|
||||
|
|
|
@ -757,8 +757,8 @@ fn make_lib_name(config: &config, auxfile: &Path, testfile: &Path) -> Path {
|
|||
|
||||
fn make_exe_name(config: &config, testfile: &Path) -> Path {
|
||||
let mut f = output_base_name(config, testfile);
|
||||
if !os::EXE_SUFFIX.is_empty() {
|
||||
match f.filename().map(|s| s + os::EXE_SUFFIX.as_bytes()) {
|
||||
if !os::consts::EXE_SUFFIX.is_empty() {
|
||||
match f.filename().map(|s| s + os::consts::EXE_SUFFIX.as_bytes()) {
|
||||
Some(v) => f.set_filename(v),
|
||||
None => ()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#[cfg(stage0)] extern mod green;
|
||||
|
||||
#[cfg(rustpkg)]
|
||||
extern mod this = "rustpkg";
|
||||
|
||||
|
|
|
@ -76,9 +76,9 @@ exceptions = [
|
|||
"rt/isaac/randport.cpp", # public domain
|
||||
"rt/isaac/rand.h", # public domain
|
||||
"rt/isaac/standard.h", # public domain
|
||||
"libstd/rt/mpsc_queue.rs", # BSD
|
||||
"libstd/rt/spsc_queue.rs", # BSD
|
||||
"libstd/rt/mpmc_bounded_queue.rs", # BSD
|
||||
"libstd/sync/mpsc_queue.rs", # BSD
|
||||
"libstd/sync/spsc_queue.rs", # BSD
|
||||
"libstd/sync/mpmc_bounded_queue.rs", # BSD
|
||||
]
|
||||
|
||||
def check_license(name, contents):
|
||||
|
|
|
@ -45,7 +45,7 @@ use sync;
|
|||
use sync::{Mutex, RWLock};
|
||||
|
||||
use std::cast;
|
||||
use std::unstable::sync::UnsafeArc;
|
||||
use std::sync::arc::UnsafeArc;
|
||||
use std::task;
|
||||
use std::borrow;
|
||||
|
||||
|
@ -127,20 +127,6 @@ impl<T:Freeze+Send> Arc<T> {
|
|||
pub fn get<'a>(&'a self) -> &'a T {
|
||||
unsafe { &*self.x.get_immut() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data back out of the Arc. This function blocks until the
|
||||
* reference given to it is the last existing one, and then unwrap the data
|
||||
* instead of destroying it.
|
||||
*
|
||||
* If multiple tasks call unwrap, all but the first will fail. Do not call
|
||||
* unwrap from a task that holds another reference to the same Arc; it is
|
||||
* guaranteed to deadlock.
|
||||
*/
|
||||
pub fn unwrap(self) -> T {
|
||||
let Arc { x: x } = self;
|
||||
x.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Freeze + Send> Clone for Arc<T> {
|
||||
|
@ -247,22 +233,6 @@ impl<T:Send> MutexArc<T> {
|
|||
cond: cond })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data, blocking until all other references are dropped,
|
||||
* exactly as arc::unwrap.
|
||||
*
|
||||
* Will additionally fail if another task has failed while accessing the arc.
|
||||
*/
|
||||
pub fn unwrap(self) -> T {
|
||||
let MutexArc { x: x } = self;
|
||||
let inner = x.unwrap();
|
||||
let MutexArcInner { failed: failed, data: data, .. } = inner;
|
||||
if failed {
|
||||
fail!("Can't unwrap poisoned MutexArc - another task failed inside!");
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Freeze + Send> MutexArc<T> {
|
||||
|
@ -503,23 +473,6 @@ impl<T:Freeze + Send> RWArc<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data, blocking until all other references are dropped,
|
||||
* exactly as arc::unwrap.
|
||||
*
|
||||
* Will additionally fail if another task has failed while accessing the arc
|
||||
* in write mode.
|
||||
*/
|
||||
pub fn unwrap(self) -> T {
|
||||
let RWArc { x: x, .. } = self;
|
||||
let inner = x.unwrap();
|
||||
let RWArcInner { failed: failed, data: data, .. } = inner;
|
||||
if failed {
|
||||
fail!("Can't unwrap poisoned RWArc - another task failed inside!")
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
// Borrowck rightly complains about immutably aliasing the rwlock in order to
|
||||
|
@ -689,22 +642,6 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
#[test] #[should_fail]
|
||||
pub fn test_mutex_arc_unwrap_poison() {
|
||||
let arc = MutexArc::new(1);
|
||||
let arc2 = ~(&arc).clone();
|
||||
let (p, c) = Chan::new();
|
||||
do task::spawn {
|
||||
arc2.access(|one| {
|
||||
c.send(());
|
||||
assert!(*one == 2);
|
||||
})
|
||||
}
|
||||
let _ = p.recv();
|
||||
let one = arc.unwrap();
|
||||
assert!(one == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsafe_mutex_arc_nested() {
|
||||
unsafe {
|
||||
|
|
|
@ -96,7 +96,6 @@ pub fn rendezvous<T: Send>() -> (SyncPort<T>, SyncChan<T>) {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use comm::{DuplexStream, rendezvous};
|
||||
use std::rt::test::run_in_uv_task;
|
||||
|
||||
|
||||
#[test]
|
||||
|
@ -124,14 +123,12 @@ mod test {
|
|||
#[test]
|
||||
fn recv_a_lot() {
|
||||
// Rendezvous streams should be able to handle any number of messages being sent
|
||||
do run_in_uv_task {
|
||||
let (port, chan) = rendezvous();
|
||||
do spawn {
|
||||
1000000.times(|| { chan.send(()) })
|
||||
}
|
||||
1000000.times(|| { port.recv() })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_and_fail_and_try_recv() {
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
|
||||
use std::borrow;
|
||||
use std::unstable::sync::{Exclusive, UnsafeArc};
|
||||
use std::unstable::atomics;
|
||||
use std::unstable::sync::Exclusive;
|
||||
use std::sync::arc::UnsafeArc;
|
||||
use std::sync::atomics;
|
||||
use std::unstable::finally::Finally;
|
||||
use std::util;
|
||||
use std::util::NonCopyable;
|
||||
|
@ -78,7 +79,7 @@ impl WaitQueue {
|
|||
|
||||
fn wait_end(&self) -> WaitEnd {
|
||||
let (wait_end, signal_end) = Chan::new();
|
||||
self.tail.send_deferred(signal_end);
|
||||
assert!(self.tail.try_send_deferred(signal_end));
|
||||
wait_end
|
||||
}
|
||||
}
|
||||
|
@ -760,7 +761,6 @@ mod tests {
|
|||
fn test_sem_runtime_friendly_blocking() {
|
||||
// Force the runtime to schedule two threads on the same sched_loop.
|
||||
// When one blocks, it should schedule the other one.
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let s = Semaphore::new(1);
|
||||
let s2 = s.clone();
|
||||
let (p, c) = Chan::new();
|
||||
|
@ -777,7 +777,6 @@ mod tests {
|
|||
});
|
||||
let _ = p.recv(); // wait for child to be done
|
||||
}
|
||||
}
|
||||
/************************************************************************
|
||||
* Mutex tests
|
||||
************************************************************************/
|
||||
|
|
|
@ -14,12 +14,9 @@
|
|||
/// parallelism.
|
||||
|
||||
|
||||
use std::task::SchedMode;
|
||||
use std::task;
|
||||
use std::vec;
|
||||
|
||||
#[cfg(test)] use std::task::SingleThreaded;
|
||||
|
||||
enum Msg<T> {
|
||||
Execute(proc(&T)),
|
||||
Quit
|
||||
|
@ -46,7 +43,6 @@ impl<T> TaskPool<T> {
|
|||
/// returns a function which, given the index of the task, should return
|
||||
/// local data to be kept around in that task.
|
||||
pub fn new(n_tasks: uint,
|
||||
opt_sched_mode: Option<SchedMode>,
|
||||
init_fn_factory: || -> proc(uint) -> T)
|
||||
-> TaskPool<T> {
|
||||
assert!(n_tasks >= 1);
|
||||
|
@ -65,18 +61,8 @@ impl<T> TaskPool<T> {
|
|||
}
|
||||
};
|
||||
|
||||
// Start the task.
|
||||
match opt_sched_mode {
|
||||
None => {
|
||||
// Run on this scheduler.
|
||||
task::spawn(task_body);
|
||||
}
|
||||
Some(sched_mode) => {
|
||||
let mut task = task::task();
|
||||
task.sched_mode(sched_mode);
|
||||
task.spawn(task_body);
|
||||
}
|
||||
}
|
||||
|
||||
chan
|
||||
});
|
||||
|
@ -99,7 +85,7 @@ fn test_task_pool() {
|
|||
let g: proc(uint) -> uint = proc(i) i;
|
||||
g
|
||||
};
|
||||
let mut pool = TaskPool::new(4, Some(SingleThreaded), f);
|
||||
let mut pool = TaskPool::new(4, f);
|
||||
8.times(|| {
|
||||
pool.execute(proc(i) println!("Hello from thread {}!", *i));
|
||||
})
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
//! This is a basic event loop implementation not meant for any "real purposes"
|
||||
//! other than testing the scheduler and proving that it's possible to have a
|
||||
//! pluggable event loop.
|
||||
//!
|
||||
//! This implementation is also used as the fallback implementation of an event
|
||||
//! loop if no other one is provided (and M:N scheduling is desired).
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use cast;
|
||||
use rt::rtio::{EventLoop, IoFactory, RemoteCallback, PausableIdleCallback,
|
||||
use std::cast;
|
||||
use std::rt::rtio::{EventLoop, IoFactory, RemoteCallback, PausableIdleCallback,
|
||||
Callback};
|
||||
use unstable::sync::Exclusive;
|
||||
use io::native;
|
||||
use util;
|
||||
use std::unstable::sync::Exclusive;
|
||||
use std::util;
|
||||
|
||||
/// This is the only exported function from this module.
|
||||
pub fn event_loop() -> ~EventLoop {
|
||||
|
@ -32,7 +32,6 @@ struct BasicLoop {
|
|||
remotes: ~[(uint, ~Callback)],
|
||||
next_remote: uint,
|
||||
messages: Exclusive<~[Message]>,
|
||||
io: ~IoFactory,
|
||||
}
|
||||
|
||||
enum Message { RunRemote(uint), RemoveRemote(uint) }
|
||||
|
@ -45,7 +44,6 @@ impl BasicLoop {
|
|||
next_remote: 0,
|
||||
remotes: ~[],
|
||||
messages: Exclusive::new(~[]),
|
||||
io: ~native::IoFactory as ~IoFactory,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,10 +157,7 @@ impl EventLoop for BasicLoop {
|
|||
~BasicRemote::new(self.messages.clone(), id) as ~RemoteCallback
|
||||
}
|
||||
|
||||
fn io<'a>(&'a mut self) -> Option<&'a mut IoFactory> {
|
||||
let factory: &mut IoFactory = self.io;
|
||||
Some(factory)
|
||||
}
|
||||
fn io<'a>(&'a mut self) -> Option<&'a mut IoFactory> { None }
|
||||
}
|
||||
|
||||
struct BasicRemote {
|
||||
|
@ -228,3 +223,61 @@ impl Drop for BasicPausable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::task::TaskOpts;
|
||||
|
||||
use basic;
|
||||
use PoolConfig;
|
||||
use SchedPool;
|
||||
|
||||
fn pool() -> SchedPool {
|
||||
SchedPool::new(PoolConfig {
|
||||
threads: 1,
|
||||
event_loop_factory: Some(basic::event_loop),
|
||||
})
|
||||
}
|
||||
|
||||
fn run(f: proc()) {
|
||||
let mut pool = pool();
|
||||
pool.spawn(TaskOpts::new(), f);
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
do run {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_channels() {
|
||||
do run {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_thread() {
|
||||
let mut pool = SchedPool::new(PoolConfig {
|
||||
threads: 2,
|
||||
event_loop_factory: Some(basic::event_loop),
|
||||
});
|
||||
|
||||
for _ in range(0, 20) {
|
||||
do pool.spawn(TaskOpts::new()) {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
}
|
||||
|
||||
pool.shutdown();
|
||||
}
|
||||
}
|
|
@ -8,14 +8,13 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use option::*;
|
||||
use super::stack::StackSegment;
|
||||
use libc::c_void;
|
||||
use uint;
|
||||
use cast::{transmute, transmute_mut_unsafe,
|
||||
use std::libc::c_void;
|
||||
use std::uint;
|
||||
use std::cast::{transmute, transmute_mut_unsafe,
|
||||
transmute_region, transmute_mut_region};
|
||||
use std::unstable::stack;
|
||||
|
||||
pub static RED_ZONE: uint = 20 * 1024;
|
||||
use stack::StackSegment;
|
||||
|
||||
// FIXME #7761: Registers is boxed so that it is 16-byte aligned, for storing
|
||||
// SSE regs. It would be marginally better not to do this. In C++ we
|
||||
|
@ -43,32 +42,47 @@ impl Context {
|
|||
|
||||
/// Create a new context that will resume execution by running proc()
|
||||
pub fn new(start: proc(), stack: &mut StackSegment) -> Context {
|
||||
// FIXME #7767: Putting main into a ~ so it's a thin pointer and can
|
||||
// be passed to the spawn function. Another unfortunate
|
||||
// allocation
|
||||
let start = ~start;
|
||||
|
||||
// The C-ABI function that is the task entry point
|
||||
//
|
||||
// Note that this function is a little sketchy. We're taking a
|
||||
// procedure, transmuting it to a stack-closure, and then calling to
|
||||
// closure. This leverages the fact that the representation of these two
|
||||
// types is the same.
|
||||
//
|
||||
// The reason that we're doing this is that this procedure is expected
|
||||
// to never return. The codegen which frees the environment of the
|
||||
// procedure occurs *after* the procedure has completed, and this means
|
||||
// that we'll never actually free the procedure.
|
||||
//
|
||||
// To solve this, we use this transmute (to not trigger the procedure
|
||||
// deallocation here), and then store a copy of the procedure in the
|
||||
// `Context` structure returned. When the `Context` is deallocated, then
|
||||
// the entire procedure box will be deallocated as well.
|
||||
extern fn task_start_wrapper(f: &proc()) {
|
||||
// XXX(pcwalton): This may be sketchy.
|
||||
unsafe {
|
||||
let f: &|| = transmute(f);
|
||||
(*f)()
|
||||
}
|
||||
}
|
||||
|
||||
let fp: *c_void = task_start_wrapper as *c_void;
|
||||
let argp: *c_void = unsafe { transmute::<&proc(), *c_void>(&*start) };
|
||||
let sp: *uint = stack.end();
|
||||
let sp: *mut uint = unsafe { transmute_mut_unsafe(sp) };
|
||||
// Save and then immediately load the current context,
|
||||
// which we will then modify to call the given function when restored
|
||||
let mut regs = new_regs();
|
||||
unsafe {
|
||||
rust_swap_registers(transmute_mut_region(&mut *regs), transmute_region(&*regs));
|
||||
rust_swap_registers(transmute_mut_region(&mut *regs),
|
||||
transmute_region(&*regs));
|
||||
};
|
||||
|
||||
initialize_call_frame(&mut *regs, fp, argp, sp);
|
||||
// FIXME #7767: Putting main into a ~ so it's a thin pointer and can
|
||||
// be passed to the spawn function. Another unfortunate
|
||||
// allocation
|
||||
let start = ~start;
|
||||
initialize_call_frame(&mut *regs,
|
||||
task_start_wrapper as *c_void,
|
||||
unsafe { transmute(&*start) },
|
||||
sp);
|
||||
|
||||
// Scheduler tasks don't have a stack in the "we allocated it" sense,
|
||||
// but rather they run on pthreads stacks. We have complete control over
|
||||
|
@ -113,17 +127,18 @@ impl Context {
|
|||
// invalid for the current task. Lucky for us `rust_swap_registers`
|
||||
// is a C function so we don't have to worry about that!
|
||||
match in_context.stack_bounds {
|
||||
Some((lo, hi)) => record_stack_bounds(lo, hi),
|
||||
Some((lo, hi)) => stack::record_stack_bounds(lo, hi),
|
||||
// If we're going back to one of the original contexts or
|
||||
// something that's possibly not a "normal task", then reset
|
||||
// the stack limit to 0 to make morestack never fail
|
||||
None => record_stack_bounds(0, uint::max_value),
|
||||
None => stack::record_stack_bounds(0, uint::max_value),
|
||||
}
|
||||
rust_swap_registers(out_regs, in_regs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "rustrt", kind = "static")]
|
||||
extern {
|
||||
fn rust_swap_registers(out_regs: *mut Registers, in_regs: *Registers);
|
||||
}
|
||||
|
@ -282,182 +297,6 @@ fn align_down(sp: *mut uint) -> *mut uint {
|
|||
// ptr::mut_offset is positive ints only
|
||||
#[inline]
|
||||
pub fn mut_offset<T>(ptr: *mut T, count: int) -> *mut T {
|
||||
use mem::size_of;
|
||||
use std::mem::size_of;
|
||||
(ptr as int + count * (size_of::<T>() as int)) as *mut T
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn record_stack_bounds(stack_lo: uint, stack_hi: uint) {
|
||||
// When the old runtime had segmented stacks, it used a calculation that was
|
||||
// "limit + RED_ZONE + FUDGE". The red zone was for things like dynamic
|
||||
// symbol resolution, llvm function calls, etc. In theory this red zone
|
||||
// value is 0, but it matters far less when we have gigantic stacks because
|
||||
// we don't need to be so exact about our stack budget. The "fudge factor"
|
||||
// was because LLVM doesn't emit a stack check for functions < 256 bytes in
|
||||
// size. Again though, we have giant stacks, so we round all these
|
||||
// calculations up to the nice round number of 20k.
|
||||
record_sp_limit(stack_lo + RED_ZONE);
|
||||
|
||||
return target_record_stack_bounds(stack_lo, stack_hi);
|
||||
|
||||
#[cfg(not(windows))] #[cfg(not(target_arch = "x86_64"))] #[inline(always)]
|
||||
unsafe fn target_record_stack_bounds(_stack_lo: uint, _stack_hi: uint) {}
|
||||
#[cfg(windows, target_arch = "x86_64")] #[inline(always)]
|
||||
unsafe fn target_record_stack_bounds(stack_lo: uint, stack_hi: uint) {
|
||||
// Windows compiles C functions which may check the stack bounds. This
|
||||
// means that if we want to perform valid FFI on windows, then we need
|
||||
// to ensure that the stack bounds are what they truly are for this
|
||||
// task. More info can be found at:
|
||||
// https://github.com/mozilla/rust/issues/3445#issuecomment-26114839
|
||||
//
|
||||
// stack range is at TIB: %gs:0x08 (top) and %gs:0x10 (bottom)
|
||||
asm!("mov $0, %gs:0x08" :: "r"(stack_hi) :: "volatile");
|
||||
asm!("mov $0, %gs:0x10" :: "r"(stack_lo) :: "volatile");
|
||||
}
|
||||
}
|
||||
|
||||
/// Records the current limit of the stack as specified by `end`.
|
||||
///
|
||||
/// This is stored in an OS-dependent location, likely inside of the thread
|
||||
/// local storage. The location that the limit is stored is a pre-ordained
|
||||
/// location because it's where LLVM has emitted code to check.
|
||||
///
|
||||
/// Note that this cannot be called under normal circumstances. This function is
|
||||
/// changing the stack limit, so upon returning any further function calls will
|
||||
/// possibly be triggering the morestack logic if you're not careful.
|
||||
///
|
||||
/// Also note that this and all of the inside functions are all flagged as
|
||||
/// "inline(always)" because they're messing around with the stack limits. This
|
||||
/// would be unfortunate for the functions themselves to trigger a morestack
|
||||
/// invocation (if they were an actual function call).
|
||||
#[inline(always)]
|
||||
pub unsafe fn record_sp_limit(limit: uint) {
|
||||
return target_record_sp_limit(limit);
|
||||
|
||||
// x86-64
|
||||
#[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
asm!("movq $$0x60+90*8, %rsi
|
||||
movq $0, %gs:(%rsi)" :: "r"(limit) : "rsi" : "volatile")
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
asm!("movq $0, %fs:112" :: "r"(limit) :: "volatile")
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
// see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
|
||||
// store this inside of the "arbitrary data slot", but double the size
|
||||
// because this is 64 bit instead of 32 bit
|
||||
asm!("movq $0, %gs:0x28" :: "r"(limit) :: "volatile")
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
asm!("movq $0, %fs:24" :: "r"(limit) :: "volatile")
|
||||
}
|
||||
|
||||
// x86
|
||||
#[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
asm!("movl $$0x48+90*4, %eax
|
||||
movl $0, %gs:(%eax)" :: "r"(limit) : "eax" : "volatile")
|
||||
}
|
||||
#[cfg(target_arch = "x86", target_os = "linux")]
|
||||
#[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
asm!("movl $0, %gs:48" :: "r"(limit) :: "volatile")
|
||||
}
|
||||
#[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
// see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
|
||||
// store this inside of the "arbitrary data slot"
|
||||
asm!("movl $0, %fs:0x14" :: "r"(limit) :: "volatile")
|
||||
}
|
||||
|
||||
// mips, arm - Some brave soul can port these to inline asm, but it's over
|
||||
// my head personally
|
||||
#[cfg(target_arch = "mips")]
|
||||
#[cfg(target_arch = "arm")] #[inline(always)]
|
||||
unsafe fn target_record_sp_limit(limit: uint) {
|
||||
return record_sp_limit(limit as *c_void);
|
||||
extern {
|
||||
fn record_sp_limit(limit: *c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The counterpart of the function above, this function will fetch the current
|
||||
/// stack limit stored in TLS.
|
||||
///
|
||||
/// Note that all of these functions are meant to be exact counterparts of their
|
||||
/// brethren above, except that the operands are reversed.
|
||||
///
|
||||
/// As with the setter, this function does not have a __morestack header and can
|
||||
/// therefore be called in a "we're out of stack" situation.
|
||||
#[inline(always)]
|
||||
// currently only called by `rust_stack_exhausted`, which doesn't
|
||||
// exist in a test build.
|
||||
#[cfg(not(test))]
|
||||
pub unsafe fn get_sp_limit() -> uint {
|
||||
return target_get_sp_limit();
|
||||
|
||||
// x86-64
|
||||
#[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movq $$0x60+90*8, %rsi
|
||||
movq %gs:(%rsi), $0" : "=r"(limit) :: "rsi" : "volatile");
|
||||
return limit;
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movq %fs:112, $0" : "=r"(limit) ::: "volatile");
|
||||
return limit;
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movq %gs:0x28, $0" : "=r"(limit) ::: "volatile");
|
||||
return limit;
|
||||
}
|
||||
#[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movq %fs:24, $0" : "=r"(limit) ::: "volatile");
|
||||
return limit;
|
||||
}
|
||||
|
||||
// x86
|
||||
#[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movl $$0x48+90*4, %eax
|
||||
movl %gs:(%eax), $0" : "=r"(limit) :: "eax" : "volatile");
|
||||
return limit;
|
||||
}
|
||||
#[cfg(target_arch = "x86", target_os = "linux")]
|
||||
#[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movl %gs:48, $0" : "=r"(limit) ::: "volatile");
|
||||
return limit;
|
||||
}
|
||||
#[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
let limit;
|
||||
asm!("movl %fs:0x14, $0" : "=r"(limit) ::: "volatile");
|
||||
return limit;
|
||||
}
|
||||
|
||||
// mips, arm - Some brave soul can port these to inline asm, but it's over
|
||||
// my head personally
|
||||
#[cfg(target_arch = "mips")]
|
||||
#[cfg(target_arch = "arm")] #[inline(always)]
|
||||
unsafe fn target_get_sp_limit() -> uint {
|
||||
return get_sp_limit() as uint;
|
||||
extern {
|
||||
fn get_sp_limit() -> *c_void;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// Coroutines represent nothing more than a context and a stack
|
||||
// segment.
|
||||
|
||||
use std::rt::env;
|
||||
|
||||
use context::Context;
|
||||
use stack::{StackPool, StackSegment};
|
||||
|
||||
/// A coroutine is nothing more than a (register context, stack) pair.
|
||||
pub struct Coroutine {
|
||||
/// The segment of stack on which the task is currently running or
|
||||
/// if the task is blocked, on which the task will resume
|
||||
/// execution.
|
||||
///
|
||||
/// Servo needs this to be public in order to tell SpiderMonkey
|
||||
/// about the stack bounds.
|
||||
current_stack_segment: StackSegment,
|
||||
|
||||
/// Always valid if the task is alive and not running.
|
||||
saved_context: Context
|
||||
}
|
||||
|
||||
impl Coroutine {
|
||||
pub fn new(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
start: proc())
|
||||
-> Coroutine {
|
||||
let stack_size = match stack_size {
|
||||
Some(size) => size,
|
||||
None => env::min_stack()
|
||||
};
|
||||
let mut stack = stack_pool.take_segment(stack_size);
|
||||
let initial_context = Context::new(start, &mut stack);
|
||||
Coroutine {
|
||||
current_stack_segment: stack,
|
||||
saved_context: initial_context
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Coroutine {
|
||||
Coroutine {
|
||||
current_stack_segment: StackSegment::new(0),
|
||||
saved_context: Context::empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy coroutine and try to reuse std::stack segment.
|
||||
pub fn recycle(self, stack_pool: &mut StackPool) {
|
||||
let Coroutine { current_stack_segment, .. } = self;
|
||||
stack_pool.give_segment(current_stack_segment);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! The "green scheduling" library
|
||||
//!
|
||||
//! This library provides M:N threading for rust programs. Internally this has
|
||||
//! the implementation of a green scheduler along with context switching and a
|
||||
//! stack-allocation strategy.
|
||||
//!
|
||||
//! This can be optionally linked in to rust programs in order to provide M:N
|
||||
//! functionality inside of 1:1 programs.
|
||||
|
||||
#[pkgid = "green#0.9-pre"];
|
||||
#[crate_id = "green#0.9-pre"];
|
||||
#[license = "MIT/ASL2"];
|
||||
#[crate_type = "rlib"];
|
||||
#[crate_type = "dylib"];
|
||||
|
||||
// NB this does *not* include globs, please keep it that way.
|
||||
#[feature(macro_rules)];
|
||||
|
||||
use std::os;
|
||||
use std::rt::crate_map;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio;
|
||||
use std::rt::task::Task;
|
||||
use std::rt::thread::Thread;
|
||||
use std::rt;
|
||||
use std::sync::atomics::{SeqCst, AtomicUint, INIT_ATOMIC_UINT};
|
||||
use std::sync::deque;
|
||||
use std::task::TaskOpts;
|
||||
use std::util;
|
||||
use std::vec;
|
||||
|
||||
use sched::{Shutdown, Scheduler, SchedHandle, TaskFromFriend, NewNeighbor};
|
||||
use sleeper_list::SleeperList;
|
||||
use stack::StackPool;
|
||||
use task::GreenTask;
|
||||
|
||||
mod macros;
|
||||
mod simple;
|
||||
|
||||
pub mod basic;
|
||||
pub mod context;
|
||||
pub mod coroutine;
|
||||
pub mod sched;
|
||||
pub mod sleeper_list;
|
||||
pub mod stack;
|
||||
pub mod task;
|
||||
|
||||
#[lang = "start"]
|
||||
#[cfg(not(test))]
|
||||
pub fn lang_start(main: *u8, argc: int, argv: **u8) -> int {
|
||||
use std::cast;
|
||||
do start(argc, argv) {
|
||||
let main: extern "Rust" fn() = unsafe { cast::transmute(main) };
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up a default runtime configuration, given compiler-supplied arguments.
|
||||
///
|
||||
/// This function will block until the entire pool of M:N schedulers have
|
||||
/// exited. This function also requires a local task to be available.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `argc` & `argv` - The argument vector. On Unix this information is used
|
||||
/// by os::args.
|
||||
/// * `main` - The initial procedure to run inside of the M:N scheduling pool.
|
||||
/// Once this procedure exits, the scheduling pool will begin to shut
|
||||
/// down. The entire pool (and this function) will only return once
|
||||
/// all child tasks have finished executing.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The return value is used as the process return code. 0 on success, 101 on
|
||||
/// error.
|
||||
pub fn start(argc: int, argv: **u8, main: proc()) -> int {
|
||||
rt::init(argc, argv);
|
||||
let mut main = Some(main);
|
||||
let mut ret = None;
|
||||
simple::task().run(|| {
|
||||
ret = Some(run(main.take_unwrap()));
|
||||
});
|
||||
// unsafe is ok b/c we're sure that the runtime is gone
|
||||
unsafe { rt::cleanup() }
|
||||
ret.unwrap()
|
||||
}
|
||||
|
||||
/// Execute the main function in a pool of M:N schedulers.
|
||||
///
|
||||
/// Configures the runtime according to the environment, by default using a task
|
||||
/// scheduler with the same number of threads as cores. Returns a process exit
|
||||
/// code.
|
||||
///
|
||||
/// This function will not return until all schedulers in the associated pool
|
||||
/// have returned.
|
||||
pub fn run(main: proc()) -> int {
|
||||
// Create a scheduler pool and spawn the main task into this pool. We will
|
||||
// get notified over a channel when the main task exits.
|
||||
let mut pool = SchedPool::new(PoolConfig::new());
|
||||
let (port, chan) = Chan::new();
|
||||
let mut opts = TaskOpts::new();
|
||||
opts.notify_chan = Some(chan);
|
||||
opts.name = Some(SendStrStatic("<main>"));
|
||||
pool.spawn(opts, main);
|
||||
|
||||
// Wait for the main task to return, and set the process error code
|
||||
// appropriately.
|
||||
if port.recv().is_err() {
|
||||
os::set_exit_status(rt::DEFAULT_ERROR_CODE);
|
||||
}
|
||||
|
||||
// Once the main task has exited and we've set our exit code, wait for all
|
||||
// spawned sub-tasks to finish running. This is done to allow all schedulers
|
||||
// to remain active while there are still tasks possibly running.
|
||||
unsafe {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().wait_for_other_tasks();
|
||||
}
|
||||
|
||||
// Now that we're sure all tasks are dead, shut down the pool of schedulers,
|
||||
// waiting for them all to return.
|
||||
pool.shutdown();
|
||||
os::get_exit_status()
|
||||
}
|
||||
|
||||
/// Configuration of how an M:N pool of schedulers is spawned.
|
||||
pub struct PoolConfig {
|
||||
/// The number of schedulers (OS threads) to spawn into this M:N pool.
|
||||
threads: uint,
|
||||
/// A factory function used to create new event loops. If this is not
|
||||
/// specified then the default event loop factory is used.
|
||||
event_loop_factory: Option<fn() -> ~rtio::EventLoop>,
|
||||
}
|
||||
|
||||
impl PoolConfig {
|
||||
/// Returns the default configuration, as determined the the environment
|
||||
/// variables of this process.
|
||||
pub fn new() -> PoolConfig {
|
||||
PoolConfig {
|
||||
threads: rt::default_sched_threads(),
|
||||
event_loop_factory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a handle to a pool of schedulers. This handle is
|
||||
/// used to keep the pool alive and also reap the status from the pool.
|
||||
pub struct SchedPool {
|
||||
priv id: uint,
|
||||
priv threads: ~[Thread<()>],
|
||||
priv handles: ~[SchedHandle],
|
||||
priv stealers: ~[deque::Stealer<~task::GreenTask>],
|
||||
priv next_friend: uint,
|
||||
priv stack_pool: StackPool,
|
||||
priv deque_pool: deque::BufferPool<~task::GreenTask>,
|
||||
priv sleepers: SleeperList,
|
||||
priv factory: fn() -> ~rtio::EventLoop,
|
||||
}
|
||||
|
||||
impl SchedPool {
|
||||
/// Execute the main function in a pool of M:N schedulers.
|
||||
///
|
||||
/// This will configure the pool according to the `config` parameter, and
|
||||
/// initially run `main` inside the pool of schedulers.
|
||||
pub fn new(config: PoolConfig) -> SchedPool {
|
||||
static mut POOL_ID: AtomicUint = INIT_ATOMIC_UINT;
|
||||
|
||||
let PoolConfig {
|
||||
threads: nscheds,
|
||||
event_loop_factory: factory
|
||||
} = config;
|
||||
let factory = factory.unwrap_or(default_event_loop_factory());
|
||||
assert!(nscheds > 0);
|
||||
|
||||
// The pool of schedulers that will be returned from this function
|
||||
let mut pool = SchedPool {
|
||||
threads: ~[],
|
||||
handles: ~[],
|
||||
stealers: ~[],
|
||||
id: unsafe { POOL_ID.fetch_add(1, SeqCst) },
|
||||
sleepers: SleeperList::new(),
|
||||
stack_pool: StackPool::new(),
|
||||
deque_pool: deque::BufferPool::new(),
|
||||
next_friend: 0,
|
||||
factory: factory,
|
||||
};
|
||||
|
||||
// Create a work queue for each scheduler, ntimes. Create an extra
|
||||
// for the main thread if that flag is set. We won't steal from it.
|
||||
let arr = vec::from_fn(nscheds, |_| pool.deque_pool.deque());
|
||||
let (workers, stealers) = vec::unzip(arr.move_iter());
|
||||
pool.stealers = stealers;
|
||||
|
||||
// Now that we've got all our work queues, create one scheduler per
|
||||
// queue, spawn the scheduler into a thread, and be sure to keep a
|
||||
// handle to the scheduler and the thread to keep them alive.
|
||||
for worker in workers.move_iter() {
|
||||
rtdebug!("inserting a regular scheduler");
|
||||
|
||||
let mut sched = ~Scheduler::new(pool.id,
|
||||
(pool.factory)(),
|
||||
worker,
|
||||
pool.stealers.clone(),
|
||||
pool.sleepers.clone());
|
||||
pool.handles.push(sched.make_handle());
|
||||
let sched = sched;
|
||||
pool.threads.push(do Thread::start {
|
||||
let mut sched = sched;
|
||||
let task = do GreenTask::new(&mut sched.stack_pool, None) {
|
||||
rtdebug!("boostraping a non-primary scheduler");
|
||||
};
|
||||
sched.bootstrap(task);
|
||||
});
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
pub fn task(&mut self, opts: TaskOpts, f: proc()) -> ~GreenTask {
|
||||
GreenTask::configure(&mut self.stack_pool, opts, f)
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, opts: TaskOpts, f: proc()) {
|
||||
let task = self.task(opts, f);
|
||||
|
||||
// Figure out someone to send this task to
|
||||
let idx = self.next_friend;
|
||||
self.next_friend += 1;
|
||||
if self.next_friend >= self.handles.len() {
|
||||
self.next_friend = 0;
|
||||
}
|
||||
|
||||
// Jettison the task away!
|
||||
self.handles[idx].send(TaskFromFriend(task));
|
||||
}
|
||||
|
||||
/// Spawns a new scheduler into this M:N pool. A handle is returned to the
|
||||
/// scheduler for use. The scheduler will not exit as long as this handle is
|
||||
/// active.
|
||||
///
|
||||
/// The scheduler spawned will participate in work stealing with all of the
|
||||
/// other schedulers currently in the scheduler pool.
|
||||
pub fn spawn_sched(&mut self) -> SchedHandle {
|
||||
let (worker, stealer) = self.deque_pool.deque();
|
||||
self.stealers.push(stealer.clone());
|
||||
|
||||
// Tell all existing schedulers about this new scheduler so they can all
|
||||
// steal work from it
|
||||
for handle in self.handles.mut_iter() {
|
||||
handle.send(NewNeighbor(stealer.clone()));
|
||||
}
|
||||
|
||||
// Create the new scheduler, using the same sleeper list as all the
|
||||
// other schedulers as well as having a stealer handle to all other
|
||||
// schedulers.
|
||||
let mut sched = ~Scheduler::new(self.id,
|
||||
(self.factory)(),
|
||||
worker,
|
||||
self.stealers.clone(),
|
||||
self.sleepers.clone());
|
||||
let ret = sched.make_handle();
|
||||
self.handles.push(sched.make_handle());
|
||||
let sched = sched;
|
||||
self.threads.push(do Thread::start {
|
||||
let mut sched = sched;
|
||||
let task = do GreenTask::new(&mut sched.stack_pool, None) {
|
||||
rtdebug!("boostraping a non-primary scheduler");
|
||||
};
|
||||
sched.bootstrap(task);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn shutdown(mut self) {
|
||||
self.stealers = ~[];
|
||||
|
||||
for mut handle in util::replace(&mut self.handles, ~[]).move_iter() {
|
||||
handle.send(Shutdown);
|
||||
}
|
||||
for thread in util::replace(&mut self.threads, ~[]).move_iter() {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SchedPool {
|
||||
fn drop(&mut self) {
|
||||
if self.threads.len() > 0 {
|
||||
fail!("dropping a M:N scheduler pool that wasn't shut down");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_event_loop_factory() -> fn() -> ~rtio::EventLoop {
|
||||
match crate_map::get_crate_map() {
|
||||
None => {}
|
||||
Some(map) => {
|
||||
match map.event_loop_factory {
|
||||
None => {}
|
||||
Some(factory) => return factory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the crate map didn't specify a factory to create an event loop, then
|
||||
// instead just use a basic event loop missing all I/O services to at least
|
||||
// get the scheduler running.
|
||||
return basic::event_loop;
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// XXX: this file probably shouldn't exist
|
||||
|
||||
#[macro_escape];
|
||||
|
||||
use std::fmt;
|
||||
use std::libc;
|
||||
|
||||
// Indicates whether we should perform expensive sanity checks, including rtassert!
|
||||
// XXX: Once the runtime matures remove the `true` below to turn off rtassert, etc.
|
||||
pub static ENFORCE_SANITY: bool = true || !cfg!(rtopt) || cfg!(rtdebug) || cfg!(rtassert);
|
||||
|
||||
macro_rules! rterrln (
|
||||
($($arg:tt)*) => ( {
|
||||
format_args!(::macros::dumb_println, $($arg)*)
|
||||
} )
|
||||
)
|
||||
|
||||
// Some basic logging. Enabled by passing `--cfg rtdebug` to the libstd build.
|
||||
macro_rules! rtdebug (
|
||||
($($arg:tt)*) => ( {
|
||||
if cfg!(rtdebug) {
|
||||
rterrln!($($arg)*)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
macro_rules! rtassert (
|
||||
( $arg:expr ) => ( {
|
||||
if ::macros::ENFORCE_SANITY {
|
||||
if !$arg {
|
||||
rtabort!(" assertion failed: {}", stringify!($arg));
|
||||
}
|
||||
}
|
||||
} )
|
||||
)
|
||||
|
||||
|
||||
macro_rules! rtabort (
|
||||
($($arg:tt)*) => ( {
|
||||
::macros::abort(format!($($arg)*));
|
||||
} )
|
||||
)
|
||||
|
||||
pub fn dumb_println(args: &fmt::Arguments) {
|
||||
use std::io;
|
||||
use std::libc;
|
||||
|
||||
struct Stderr;
|
||||
impl io::Writer for Stderr {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
libc::write(libc::STDERR_FILENO,
|
||||
data.as_ptr() as *libc::c_void,
|
||||
data.len() as libc::size_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut w = Stderr;
|
||||
fmt::writeln(&mut w as &mut io::Writer, args);
|
||||
}
|
||||
|
||||
pub fn abort(msg: &str) -> ! {
|
||||
let msg = if !msg.is_empty() { msg } else { "aborted" };
|
||||
let hash = msg.chars().fold(0, |accum, val| accum + (val as uint) );
|
||||
let quote = match hash % 10 {
|
||||
0 => "
|
||||
It was from the artists and poets that the pertinent answers came, and I
|
||||
know that panic would have broken loose had they been able to compare notes.
|
||||
As it was, lacking their original letters, I half suspected the compiler of
|
||||
having asked leading questions, or of having edited the correspondence in
|
||||
corroboration of what he had latently resolved to see.",
|
||||
1 => "
|
||||
There are not many persons who know what wonders are opened to them in the
|
||||
stories and visions of their youth; for when as children we listen and dream,
|
||||
we think but half-formed thoughts, and when as men we try to remember, we are
|
||||
dulled and prosaic with the poison of life. But some of us awake in the night
|
||||
with strange phantasms of enchanted hills and gardens, of fountains that sing
|
||||
in the sun, of golden cliffs overhanging murmuring seas, of plains that stretch
|
||||
down to sleeping cities of bronze and stone, and of shadowy companies of heroes
|
||||
that ride caparisoned white horses along the edges of thick forests; and then
|
||||
we know that we have looked back through the ivory gates into that world of
|
||||
wonder which was ours before we were wise and unhappy.",
|
||||
2 => "
|
||||
Instead of the poems I had hoped for, there came only a shuddering blackness
|
||||
and ineffable loneliness; and I saw at last a fearful truth which no one had
|
||||
ever dared to breathe before — the unwhisperable secret of secrets — The fact
|
||||
that this city of stone and stridor is not a sentient perpetuation of Old New
|
||||
York as London is of Old London and Paris of Old Paris, but that it is in fact
|
||||
quite dead, its sprawling body imperfectly embalmed and infested with queer
|
||||
animate things which have nothing to do with it as it was in life.",
|
||||
3 => "
|
||||
The ocean ate the last of the land and poured into the smoking gulf, thereby
|
||||
giving up all it had ever conquered. From the new-flooded lands it flowed
|
||||
again, uncovering death and decay; and from its ancient and immemorial bed it
|
||||
trickled loathsomely, uncovering nighted secrets of the years when Time was
|
||||
young and the gods unborn. Above the waves rose weedy remembered spires. The
|
||||
moon laid pale lilies of light on dead London, and Paris stood up from its damp
|
||||
grave to be sanctified with star-dust. Then rose spires and monoliths that were
|
||||
weedy but not remembered; terrible spires and monoliths of lands that men never
|
||||
knew were lands...",
|
||||
4 => "
|
||||
There was a night when winds from unknown spaces whirled us irresistibly into
|
||||
limitless vacuum beyond all thought and entity. Perceptions of the most
|
||||
maddeningly untransmissible sort thronged upon us; perceptions of infinity
|
||||
which at the time convulsed us with joy, yet which are now partly lost to my
|
||||
memory and partly incapable of presentation to others.",
|
||||
_ => "You've met with a terrible fate, haven't you?"
|
||||
};
|
||||
rterrln!("{}", "");
|
||||
rterrln!("{}", quote);
|
||||
rterrln!("{}", "");
|
||||
rterrln!("fatal runtime error: {}", msg);
|
||||
|
||||
abort();
|
||||
|
||||
fn abort() -> ! {
|
||||
unsafe { libc::abort() }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! A small module implementing a simple "runtime" used for bootstrapping a rust
|
||||
//! scheduler pool and then interacting with it.
|
||||
|
||||
use std::cast;
|
||||
use std::rt::Runtime;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio;
|
||||
use std::rt::task::{Task, BlockedTask};
|
||||
use std::task::TaskOpts;
|
||||
use std::unstable::sync::LittleLock;
|
||||
|
||||
struct SimpleTask {
|
||||
lock: LittleLock,
|
||||
awoken: bool,
|
||||
}
|
||||
|
||||
impl Runtime for SimpleTask {
|
||||
// Implement the simple tasks of descheduling and rescheduling, but only in
|
||||
// a simple number of cases.
|
||||
fn deschedule(mut ~self, times: uint, mut cur_task: ~Task,
|
||||
f: |BlockedTask| -> Result<(), BlockedTask>) {
|
||||
assert!(times == 1);
|
||||
|
||||
let me = &mut *self as *mut SimpleTask;
|
||||
let cur_dupe = &*cur_task as *Task;
|
||||
cur_task.put_runtime(self as ~Runtime);
|
||||
let task = BlockedTask::block(cur_task);
|
||||
|
||||
// See libnative/task.rs for what's going on here with the `awoken`
|
||||
// field and the while loop around wait()
|
||||
unsafe {
|
||||
let mut guard = (*me).lock.lock();
|
||||
(*me).awoken = false;
|
||||
match f(task) {
|
||||
Ok(()) => {
|
||||
while !(*me).awoken {
|
||||
guard.wait();
|
||||
}
|
||||
}
|
||||
Err(task) => { cast::forget(task.wake()); }
|
||||
}
|
||||
drop(guard);
|
||||
cur_task = cast::transmute(cur_dupe);
|
||||
}
|
||||
Local::put(cur_task);
|
||||
}
|
||||
fn reawaken(mut ~self, mut to_wake: ~Task, _can_resched: bool) {
|
||||
let me = &mut *self as *mut SimpleTask;
|
||||
to_wake.put_runtime(self as ~Runtime);
|
||||
unsafe {
|
||||
cast::forget(to_wake);
|
||||
let _l = (*me).lock.lock();
|
||||
(*me).awoken = true;
|
||||
(*me).lock.signal();
|
||||
}
|
||||
}
|
||||
|
||||
// These functions are all unimplemented and fail as a result. This is on
|
||||
// purpose. A "simple task" is just that, a very simple task that can't
|
||||
// really do a whole lot. The only purpose of the task is to get us off our
|
||||
// feet and running.
|
||||
fn yield_now(~self, _cur_task: ~Task) { fail!() }
|
||||
fn maybe_yield(~self, _cur_task: ~Task) { fail!() }
|
||||
fn spawn_sibling(~self, _cur_task: ~Task, _opts: TaskOpts, _f: proc()) {
|
||||
fail!()
|
||||
}
|
||||
fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> { None }
|
||||
fn wrap(~self) -> ~Any { fail!() }
|
||||
}
|
||||
|
||||
pub fn task() -> ~Task {
|
||||
let mut task = ~Task::new();
|
||||
task.put_runtime(~SimpleTask {
|
||||
lock: LittleLock::new(),
|
||||
awoken: false,
|
||||
} as ~Runtime);
|
||||
return task;
|
||||
}
|
|
@ -11,10 +11,9 @@
|
|||
//! Maintains a shared list of sleeping schedulers. Schedulers
|
||||
//! use this to wake each other up.
|
||||
|
||||
use rt::sched::SchedHandle;
|
||||
use rt::mpmc_bounded_queue::Queue;
|
||||
use option::*;
|
||||
use clone::Clone;
|
||||
use std::sync::mpmc_bounded_queue::Queue;
|
||||
|
||||
use sched::SchedHandle;
|
||||
|
||||
pub struct SleeperList {
|
||||
priv q: Queue<SchedHandle>,
|
|
@ -8,11 +8,8 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use container::Container;
|
||||
use ptr::RawPtr;
|
||||
use vec;
|
||||
use ops::Drop;
|
||||
use libc::{c_uint, uintptr_t};
|
||||
use std::vec;
|
||||
use std::libc::{c_uint, uintptr_t};
|
||||
|
||||
pub struct StackSegment {
|
||||
priv buf: ~[u8],
|
|
@ -0,0 +1,536 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! The Green Task implementation
|
||||
//!
|
||||
//! This module contains the glue to the libstd runtime necessary to integrate
|
||||
//! M:N scheduling. This GreenTask structure is hidden as a trait object in all
|
||||
//! rust tasks and virtual calls are made in order to interface with it.
|
||||
//!
|
||||
//! Each green task contains a scheduler if it is currently running, and it also
|
||||
//! contains the rust task itself in order to juggle around ownership of the
|
||||
//! values.
|
||||
|
||||
use std::cast;
|
||||
use std::rt::Runtime;
|
||||
use std::rt::rtio;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::task::{Task, BlockedTask};
|
||||
use std::task::TaskOpts;
|
||||
use std::unstable::mutex::Mutex;
|
||||
|
||||
use coroutine::Coroutine;
|
||||
use sched::{Scheduler, SchedHandle, RunOnce};
|
||||
use stack::StackPool;
|
||||
|
||||
/// The necessary fields needed to keep track of a green task (as opposed to a
|
||||
/// 1:1 task).
|
||||
pub struct GreenTask {
|
||||
coroutine: Option<Coroutine>,
|
||||
handle: Option<SchedHandle>,
|
||||
sched: Option<~Scheduler>,
|
||||
task: Option<~Task>,
|
||||
task_type: TaskType,
|
||||
pool_id: uint,
|
||||
|
||||
// See the comments in the scheduler about why this is necessary
|
||||
nasty_deschedule_lock: Mutex,
|
||||
}
|
||||
|
||||
pub enum TaskType {
|
||||
TypeGreen(Option<Home>),
|
||||
TypeSched,
|
||||
}
|
||||
|
||||
pub enum Home {
|
||||
AnySched,
|
||||
HomeSched(SchedHandle),
|
||||
}
|
||||
|
||||
impl GreenTask {
|
||||
/// Creates a new green task which is not homed to any particular scheduler
|
||||
/// and will not have any contained Task structure.
|
||||
pub fn new(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
start: proc()) -> ~GreenTask {
|
||||
GreenTask::new_homed(stack_pool, stack_size, AnySched, start)
|
||||
}
|
||||
|
||||
/// Creates a new task (like `new`), but specifies the home for new task.
|
||||
pub fn new_homed(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
home: Home,
|
||||
start: proc()) -> ~GreenTask {
|
||||
let mut ops = GreenTask::new_typed(None, TypeGreen(Some(home)));
|
||||
let start = GreenTask::build_start_wrapper(start, ops.as_uint());
|
||||
ops.coroutine = Some(Coroutine::new(stack_pool, stack_size, start));
|
||||
return ops;
|
||||
}
|
||||
|
||||
/// Creates a new green task with the specified coroutine and type, this is
|
||||
/// useful when creating scheduler tasks.
|
||||
pub fn new_typed(coroutine: Option<Coroutine>,
|
||||
task_type: TaskType) -> ~GreenTask {
|
||||
~GreenTask {
|
||||
pool_id: 0,
|
||||
coroutine: coroutine,
|
||||
task_type: task_type,
|
||||
sched: None,
|
||||
handle: None,
|
||||
nasty_deschedule_lock: unsafe { Mutex::new() },
|
||||
task: Some(~Task::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new green task with the given configuration options for the
|
||||
/// contained Task object. The given stack pool is also used to allocate a
|
||||
/// new stack for this task.
|
||||
pub fn configure(pool: &mut StackPool,
|
||||
opts: TaskOpts,
|
||||
f: proc()) -> ~GreenTask {
|
||||
let TaskOpts {
|
||||
watched: _watched,
|
||||
notify_chan, name, stack_size
|
||||
} = opts;
|
||||
|
||||
let mut green = GreenTask::new(pool, stack_size, f);
|
||||
{
|
||||
let task = green.task.get_mut_ref();
|
||||
task.name = name;
|
||||
match notify_chan {
|
||||
Some(chan) => {
|
||||
let on_exit = proc(task_result) { chan.send(task_result) };
|
||||
task.death.on_exit = Some(on_exit);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
return green;
|
||||
}
|
||||
|
||||
/// Just like the `maybe_take_runtime` function, this function should *not*
|
||||
/// exist. Usage of this function is _strongly_ discouraged. This is an
|
||||
/// absolute last resort necessary for converting a libstd task to a green
|
||||
/// task.
|
||||
///
|
||||
/// This function will assert that the task is indeed a green task before
|
||||
/// returning (and will kill the entire process if this is wrong).
|
||||
pub fn convert(mut task: ~Task) -> ~GreenTask {
|
||||
match task.maybe_take_runtime::<GreenTask>() {
|
||||
Some(mut green) => {
|
||||
green.put_task(task);
|
||||
green
|
||||
}
|
||||
None => rtabort!("not a green task any more?"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a function which is the actual starting execution point for a
|
||||
/// rust task. This function is the glue necessary to execute the libstd
|
||||
/// task and then clean up the green thread after it exits.
|
||||
///
|
||||
/// The second argument to this function is actually a transmuted copy of
|
||||
/// the `GreenTask` pointer. Context switches in the scheduler silently
|
||||
/// transfer ownership of the `GreenTask` to the other end of the context
|
||||
/// switch, so because this is the first code that is running in this task,
|
||||
/// it must first re-acquire ownership of the green task.
|
||||
pub fn build_start_wrapper(start: proc(), ops: uint) -> proc() {
|
||||
proc() {
|
||||
// First code after swap to this new context. Run our
|
||||
// cleanup job after we have re-acquired ownership of the green
|
||||
// task.
|
||||
let mut task: ~GreenTask = unsafe { GreenTask::from_uint(ops) };
|
||||
task.sched.get_mut_ref().run_cleanup_job();
|
||||
|
||||
// Convert our green task to a libstd task and then execute the code
|
||||
// requeted. This is the "try/catch" block for this green task and
|
||||
// is the wrapper for *all* code run in the task.
|
||||
let mut start = Some(start);
|
||||
let task = task.swap().run(|| start.take_unwrap()());
|
||||
|
||||
// Once the function has exited, it's time to run the termination
|
||||
// routine. This means we need to context switch one more time but
|
||||
// clean ourselves up on the other end. Since we have no way of
|
||||
// preserving a handle to the GreenTask down to this point, this
|
||||
// unfortunately must call `GreenTask::convert`. In order to avoid
|
||||
// this we could add a `terminate` function to the `Runtime` trait
|
||||
// in libstd, but that seems less appropriate since the coversion
|
||||
// method exists.
|
||||
GreenTask::convert(task).terminate();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn give_home(&mut self, new_home: Home) {
|
||||
match self.task_type {
|
||||
TypeGreen(ref mut home) => { *home = Some(new_home); }
|
||||
TypeSched => rtabort!("type error: used SchedTask as GreenTask"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_unwrap_home(&mut self) -> Home {
|
||||
match self.task_type {
|
||||
TypeGreen(ref mut home) => home.take_unwrap(),
|
||||
TypeSched => rtabort!("type error: used SchedTask as GreenTask"),
|
||||
}
|
||||
}
|
||||
|
||||
// New utility functions for homes.
|
||||
|
||||
pub fn is_home_no_tls(&self, sched: &Scheduler) -> bool {
|
||||
match self.task_type {
|
||||
TypeGreen(Some(AnySched)) => { false }
|
||||
TypeGreen(Some(HomeSched(SchedHandle { sched_id: ref id, .. }))) => {
|
||||
*id == sched.sched_id()
|
||||
}
|
||||
TypeGreen(None) => { rtabort!("task without home"); }
|
||||
TypeSched => {
|
||||
// Awe yea
|
||||
rtabort!("type error: expected: TypeGreen, found: TaskSched");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn homed(&self) -> bool {
|
||||
match self.task_type {
|
||||
TypeGreen(Some(AnySched)) => { false }
|
||||
TypeGreen(Some(HomeSched(SchedHandle { .. }))) => { true }
|
||||
TypeGreen(None) => {
|
||||
rtabort!("task without home");
|
||||
}
|
||||
TypeSched => {
|
||||
rtabort!("type error: expected: TypeGreen, found: TaskSched");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_sched(&self) -> bool {
|
||||
match self.task_type {
|
||||
TypeGreen(..) => false, TypeSched => true,
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe functions for transferring ownership of this GreenTask across
|
||||
// context switches
|
||||
|
||||
pub fn as_uint(&self) -> uint {
|
||||
unsafe { cast::transmute(self) }
|
||||
}
|
||||
|
||||
pub unsafe fn from_uint(val: uint) -> ~GreenTask { cast::transmute(val) }
|
||||
|
||||
// Runtime glue functions and helpers
|
||||
|
||||
pub fn put_with_sched(mut ~self, sched: ~Scheduler) {
|
||||
assert!(self.sched.is_none());
|
||||
self.sched = Some(sched);
|
||||
self.put();
|
||||
}
|
||||
|
||||
pub fn put_task(&mut self, task: ~Task) {
|
||||
assert!(self.task.is_none());
|
||||
self.task = Some(task);
|
||||
}
|
||||
|
||||
pub fn swap(mut ~self) -> ~Task {
|
||||
let mut task = self.task.take_unwrap();
|
||||
task.put_runtime(self as ~Runtime);
|
||||
return task;
|
||||
}
|
||||
|
||||
pub fn put(~self) {
|
||||
assert!(self.sched.is_some());
|
||||
Local::put(self.swap());
|
||||
}
|
||||
|
||||
fn terminate(mut ~self) {
|
||||
let sched = self.sched.take_unwrap();
|
||||
sched.terminate_current_task(self);
|
||||
}
|
||||
|
||||
// This function is used to remotely wakeup this green task back on to its
|
||||
// original pool of schedulers. In order to do so, each tasks arranges a
|
||||
// SchedHandle upon descheduling to be available for sending itself back to
|
||||
// the original pool.
|
||||
//
|
||||
// Note that there is an interesting transfer of ownership going on here. We
|
||||
// must relinquish ownership of the green task, but then also send the task
|
||||
// over the handle back to the original scheduler. In order to safely do
|
||||
// this, we leverage the already-present "nasty descheduling lock". The
|
||||
// reason for doing this is that each task will bounce on this lock after
|
||||
// resuming after a context switch. By holding the lock over the enqueueing
|
||||
// of the task, we're guaranteed that the SchedHandle's memory will be valid
|
||||
// for this entire function.
|
||||
//
|
||||
// An alternative would include having incredibly cheaply cloneable handles,
|
||||
// but right now a SchedHandle is something like 6 allocations, so it is
|
||||
// *not* a cheap operation to clone a handle. Until the day comes that we
|
||||
// need to optimize this, a lock should do just fine (it's completely
|
||||
// uncontended except for when the task is rescheduled).
|
||||
fn reawaken_remotely(mut ~self) {
|
||||
unsafe {
|
||||
let mtx = &mut self.nasty_deschedule_lock as *mut Mutex;
|
||||
let handle = self.handle.get_mut_ref() as *mut SchedHandle;
|
||||
(*mtx).lock();
|
||||
(*handle).send(RunOnce(self));
|
||||
(*mtx).unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime for GreenTask {
|
||||
fn yield_now(mut ~self, cur_task: ~Task) {
|
||||
self.put_task(cur_task);
|
||||
let sched = self.sched.take_unwrap();
|
||||
sched.yield_now(self);
|
||||
}
|
||||
|
||||
fn maybe_yield(mut ~self, cur_task: ~Task) {
|
||||
self.put_task(cur_task);
|
||||
let sched = self.sched.take_unwrap();
|
||||
sched.maybe_yield(self);
|
||||
}
|
||||
|
||||
fn deschedule(mut ~self, times: uint, cur_task: ~Task,
|
||||
f: |BlockedTask| -> Result<(), BlockedTask>) {
|
||||
self.put_task(cur_task);
|
||||
let mut sched = self.sched.take_unwrap();
|
||||
|
||||
// In order for this task to be reawoken in all possible contexts, we
|
||||
// may need a handle back in to the current scheduler. When we're woken
|
||||
// up in anything other than the local scheduler pool, this handle is
|
||||
// used to send this task back into the scheduler pool.
|
||||
if self.handle.is_none() {
|
||||
self.handle = Some(sched.make_handle());
|
||||
self.pool_id = sched.pool_id;
|
||||
}
|
||||
|
||||
// This code is pretty standard, except for the usage of
|
||||
// `GreenTask::convert`. Right now if we use `reawaken` directly it will
|
||||
// expect for there to be a task in local TLS, but that is not true for
|
||||
// this deschedule block (because the scheduler must retain ownership of
|
||||
// the task while the cleanup job is running). In order to get around
|
||||
// this for now, we invoke the scheduler directly with the converted
|
||||
// Task => GreenTask structure.
|
||||
if times == 1 {
|
||||
sched.deschedule_running_task_and_then(self, |sched, task| {
|
||||
match f(task) {
|
||||
Ok(()) => {}
|
||||
Err(t) => {
|
||||
t.wake().map(|t| {
|
||||
sched.enqueue_task(GreenTask::convert(t))
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sched.deschedule_running_task_and_then(self, |sched, task| {
|
||||
for task in task.make_selectable(times) {
|
||||
match f(task) {
|
||||
Ok(()) => {},
|
||||
Err(task) => {
|
||||
task.wake().map(|t| {
|
||||
sched.enqueue_task(GreenTask::convert(t))
|
||||
});
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn reawaken(mut ~self, to_wake: ~Task, can_resched: bool) {
|
||||
self.put_task(to_wake);
|
||||
assert!(self.sched.is_none());
|
||||
|
||||
// Waking up a green thread is a bit of a tricky situation. We have no
|
||||
// guarantee about where the current task is running. The options we
|
||||
// have for where this current task is running are:
|
||||
//
|
||||
// 1. Our original scheduler pool
|
||||
// 2. Some other scheduler pool
|
||||
// 3. Something that isn't a scheduler pool
|
||||
//
|
||||
// In order to figure out what case we're in, this is the reason that
|
||||
// the `maybe_take_runtime` function exists. Using this function we can
|
||||
// dynamically check to see which of these cases is the current
|
||||
// situation and then dispatch accordingly.
|
||||
//
|
||||
// In case 1, we just use the local scheduler to resume ourselves
|
||||
// immediately (if a rescheduling is possible).
|
||||
//
|
||||
// In case 2 and 3, we need to remotely reawaken ourself in order to be
|
||||
// transplanted back to the correct scheduler pool.
|
||||
let mut running_task: ~Task = Local::take();
|
||||
match running_task.maybe_take_runtime::<GreenTask>() {
|
||||
Some(mut running_green_task) => {
|
||||
running_green_task.put_task(running_task);
|
||||
let mut sched = running_green_task.sched.take_unwrap();
|
||||
|
||||
if sched.pool_id == self.pool_id {
|
||||
if can_resched {
|
||||
sched.run_task(running_green_task, self);
|
||||
} else {
|
||||
sched.enqueue_task(self);
|
||||
running_green_task.put_with_sched(sched);
|
||||
}
|
||||
} else {
|
||||
self.reawaken_remotely();
|
||||
|
||||
// put that thing back where it came from!
|
||||
running_green_task.put_with_sched(sched);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.reawaken_remotely();
|
||||
Local::put(running_task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_sibling(mut ~self, cur_task: ~Task, opts: TaskOpts, f: proc()) {
|
||||
self.put_task(cur_task);
|
||||
|
||||
// Spawns a task into the current scheduler. We allocate the new task's
|
||||
// stack from the scheduler's stack pool, and then configure it
|
||||
// accordingly to `opts`. Afterwards we bootstrap it immediately by
|
||||
// switching to it.
|
||||
//
|
||||
// Upon returning, our task is back in TLS and we're good to return.
|
||||
let mut sched = self.sched.take_unwrap();
|
||||
let sibling = GreenTask::configure(&mut sched.stack_pool, opts, f);
|
||||
sched.run_task(self, sibling)
|
||||
}
|
||||
|
||||
// Local I/O is provided by the scheduler's event loop
|
||||
fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> {
|
||||
match self.sched.get_mut_ref().event_loop.io() {
|
||||
Some(io) => Some(rtio::LocalIo::new(io)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap(~self) -> ~Any { self as ~Any }
|
||||
}
|
||||
|
||||
impl Drop for GreenTask {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.nasty_deschedule_lock.destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rt::Runtime;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::task::Task;
|
||||
use std::task;
|
||||
use std::task::TaskOpts;
|
||||
|
||||
use super::super::{PoolConfig, SchedPool};
|
||||
use super::GreenTask;
|
||||
|
||||
fn spawn_opts(opts: TaskOpts, f: proc()) {
|
||||
let mut pool = SchedPool::new(PoolConfig {
|
||||
threads: 1,
|
||||
event_loop_factory: None,
|
||||
});
|
||||
pool.spawn(opts, f);
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn_opts(TaskOpts::new()) {
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_fail() {
|
||||
let (p, c) = Chan::<()>::new();
|
||||
do spawn_opts(TaskOpts::new()) {
|
||||
let _c = c;
|
||||
fail!()
|
||||
}
|
||||
assert_eq!(p.recv_opt(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_opts() {
|
||||
let mut opts = TaskOpts::new();
|
||||
opts.name = Some(SendStrStatic("test"));
|
||||
opts.stack_size = Some(20 * 4096);
|
||||
let (p, c) = Chan::new();
|
||||
opts.notify_chan = Some(c);
|
||||
spawn_opts(opts, proc() {});
|
||||
assert!(p.recv().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_opts_fail() {
|
||||
let mut opts = TaskOpts::new();
|
||||
let (p, c) = Chan::new();
|
||||
opts.notify_chan = Some(c);
|
||||
spawn_opts(opts, proc() { fail!() });
|
||||
assert!(p.recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yield_test() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn_opts(TaskOpts::new()) {
|
||||
10.times(task::deschedule);
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_children() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn_opts(TaskOpts::new()) {
|
||||
let (p, c2) = Chan::new();
|
||||
do spawn {
|
||||
let (p, c3) = Chan::new();
|
||||
do spawn {
|
||||
c3.send(());
|
||||
}
|
||||
p.recv();
|
||||
c2.send(());
|
||||
}
|
||||
p.recv();
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_inherits() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn_opts(TaskOpts::new()) {
|
||||
let c = c;
|
||||
do spawn {
|
||||
let mut task: ~Task = Local::take();
|
||||
match task.maybe_take_runtime::<GreenTask>() {
|
||||
Some(ops) => {
|
||||
task.put_runtime(ops as ~Runtime);
|
||||
}
|
||||
None => fail!(),
|
||||
}
|
||||
Local::put(task);
|
||||
c.send(());
|
||||
}
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
}
|
|
@ -10,28 +10,21 @@
|
|||
|
||||
//! Blocking posix-based file I/O
|
||||
|
||||
#[allow(non_camel_case_types)];
|
||||
use std::c_str::CString;
|
||||
use std::io::IoError;
|
||||
use std::io;
|
||||
use std::libc::c_int;
|
||||
use std::libc;
|
||||
use std::os;
|
||||
use std::rt::rtio;
|
||||
use std::unstable::intrinsics;
|
||||
use std::vec;
|
||||
|
||||
use c_str::CString;
|
||||
use io::IoError;
|
||||
use io;
|
||||
use libc::c_int;
|
||||
use libc;
|
||||
use ops::Drop;
|
||||
use option::{Some, None, Option};
|
||||
use os;
|
||||
use path::{Path, GenericPath};
|
||||
use ptr::RawPtr;
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::rtio;
|
||||
use super::IoResult;
|
||||
use unstable::intrinsics;
|
||||
use vec::ImmutableVector;
|
||||
use vec;
|
||||
|
||||
#[cfg(windows)] use os::win32::{as_utf16_p, fill_utf16_buf_and_decode};
|
||||
#[cfg(windows)] use ptr;
|
||||
#[cfg(windows)] use str;
|
||||
#[cfg(windows)] use std::os::win32::{as_utf16_p, fill_utf16_buf_and_decode};
|
||||
#[cfg(windows)] use std::ptr;
|
||||
#[cfg(windows)] use std::str;
|
||||
|
||||
fn keep_going(data: &[u8], f: |*u8, uint| -> i64) -> i64 {
|
||||
#[cfg(windows)] static eintr: int = 0; // doesn't matter
|
||||
|
@ -490,8 +483,8 @@ pub fn readdir(p: &CString) -> IoResult<~[Path]> {
|
|||
unsafe {
|
||||
#[cfg(not(windows))]
|
||||
unsafe fn get_list(p: &CString) -> IoResult<~[Path]> {
|
||||
use libc::{dirent_t};
|
||||
use libc::{opendir, readdir, closedir};
|
||||
use std::libc::{dirent_t};
|
||||
use std::libc::{opendir, readdir, closedir};
|
||||
extern {
|
||||
fn rust_list_dir_val(ptr: *dirent_t) -> *libc::c_char;
|
||||
}
|
||||
|
@ -517,14 +510,14 @@ pub fn readdir(p: &CString) -> IoResult<~[Path]> {
|
|||
|
||||
#[cfg(windows)]
|
||||
unsafe fn get_list(p: &CString) -> IoResult<~[Path]> {
|
||||
use libc::consts::os::extra::INVALID_HANDLE_VALUE;
|
||||
use libc::{wcslen, free};
|
||||
use libc::funcs::extra::kernel32::{
|
||||
use std::libc::consts::os::extra::INVALID_HANDLE_VALUE;
|
||||
use std::libc::{wcslen, free};
|
||||
use std::libc::funcs::extra::kernel32::{
|
||||
FindFirstFileW,
|
||||
FindNextFileW,
|
||||
FindClose,
|
||||
};
|
||||
use libc::types::os::arch::extra::HANDLE;
|
||||
use std::libc::types::os::arch::extra::HANDLE;
|
||||
use os::win32::{
|
||||
as_utf16_p
|
||||
};
|
||||
|
@ -906,12 +899,11 @@ pub fn utime(p: &CString, atime: u64, mtime: u64) -> IoResult<()> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use io::native::file::{CFile, FileDesc};
|
||||
use io;
|
||||
use libc;
|
||||
use os;
|
||||
use result::Ok;
|
||||
use rt::rtio::RtioFileStream;
|
||||
use super::{CFile, FileDesc};
|
||||
use std::io;
|
||||
use std::libc;
|
||||
use std::os;
|
||||
use std::rt::rtio::RtioFileStream;
|
||||
|
||||
#[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer
|
||||
#[test]
|
|
@ -21,24 +21,21 @@
|
|||
//! play. The only dependencies of these modules are the normal system libraries
|
||||
//! that you would find on the respective platform.
|
||||
|
||||
use c_str::CString;
|
||||
use comm::SharedChan;
|
||||
use libc::c_int;
|
||||
use libc;
|
||||
use option::{Option, None, Some};
|
||||
use os;
|
||||
use path::Path;
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::rtio;
|
||||
use rt::rtio::{RtioTcpStream, RtioTcpListener, RtioUdpSocket, RtioUnixListener,
|
||||
RtioPipe, RtioFileStream, RtioProcess, RtioSignal, RtioTTY,
|
||||
CloseBehavior, RtioTimer};
|
||||
use io;
|
||||
use io::IoError;
|
||||
use io::net::ip::SocketAddr;
|
||||
use io::process::ProcessConfig;
|
||||
use io::signal::Signum;
|
||||
use ai = io::net::addrinfo;
|
||||
use std::c_str::CString;
|
||||
use std::comm::SharedChan;
|
||||
use std::libc::c_int;
|
||||
use std::libc;
|
||||
use std::os;
|
||||
use std::rt::rtio;
|
||||
use std::rt::rtio::{RtioTcpStream, RtioTcpListener, RtioUdpSocket,
|
||||
RtioUnixListener, RtioPipe, RtioFileStream, RtioProcess,
|
||||
RtioSignal, RtioTTY, CloseBehavior, RtioTimer};
|
||||
use std::io;
|
||||
use std::io::IoError;
|
||||
use std::io::net::ip::SocketAddr;
|
||||
use std::io::process::ProcessConfig;
|
||||
use std::io::signal::Signum;
|
||||
use ai = std::io::net::addrinfo;
|
||||
|
||||
// Local re-exports
|
||||
pub use self::file::FileDesc;
|
||||
|
@ -223,6 +220,3 @@ impl rtio::IoFactory for IoFactory {
|
|||
Err(unimpl())
|
||||
}
|
||||
}
|
||||
|
||||
pub static mut NATIVE_IO_FACTORY: IoFactory = IoFactory;
|
||||
|
|
@ -8,18 +8,17 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use io;
|
||||
use libc::{pid_t, c_void, c_int};
|
||||
use libc;
|
||||
use os;
|
||||
use prelude::*;
|
||||
use ptr;
|
||||
use rt::rtio;
|
||||
use super::file;
|
||||
#[cfg(windows)]
|
||||
use cast;
|
||||
use std::io;
|
||||
use std::libc::{pid_t, c_void, c_int};
|
||||
use std::libc;
|
||||
use std::os;
|
||||
use std::ptr;
|
||||
use std::rt::rtio;
|
||||
use p = std::io::process;
|
||||
|
||||
use p = io::process;
|
||||
#[cfg(windows)] use std::cast;
|
||||
|
||||
use super::file;
|
||||
|
||||
/**
|
||||
* A value representing a child process.
|
||||
|
@ -179,22 +178,22 @@ fn spawn_process_os(prog: &str, args: &[~str],
|
|||
env: Option<~[(~str, ~str)]>,
|
||||
dir: Option<&Path>,
|
||||
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
|
||||
use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
|
||||
use libc::consts::os::extra::{
|
||||
use std::libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO};
|
||||
use std::libc::consts::os::extra::{
|
||||
TRUE, FALSE,
|
||||
STARTF_USESTDHANDLES,
|
||||
INVALID_HANDLE_VALUE,
|
||||
DUPLICATE_SAME_ACCESS
|
||||
};
|
||||
use libc::funcs::extra::kernel32::{
|
||||
use std::libc::funcs::extra::kernel32::{
|
||||
GetCurrentProcess,
|
||||
DuplicateHandle,
|
||||
CloseHandle,
|
||||
CreateProcessA
|
||||
};
|
||||
use libc::funcs::extra::msvcrt::get_osfhandle;
|
||||
use std::libc::funcs::extra::msvcrt::get_osfhandle;
|
||||
|
||||
use mem;
|
||||
use std::mem;
|
||||
|
||||
unsafe {
|
||||
|
||||
|
@ -256,10 +255,10 @@ fn spawn_process_os(prog: &str, args: &[~str],
|
|||
fail!("failure in CreateProcess: {}", *msg);
|
||||
}
|
||||
|
||||
// We close the thread handle because we don't care about keeping the
|
||||
// We close the thread handle because std::we don't care about keeping the
|
||||
// thread id valid, and we aren't keeping the thread handle around to be
|
||||
// able to close it later. We don't close the process handle however
|
||||
// because we want the process id to stay valid at least until the
|
||||
// because std::we want the process id to stay valid at least until the
|
||||
// calling code closes the process handle.
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
|
@ -362,8 +361,8 @@ fn spawn_process_os(prog: &str, args: &[~str],
|
|||
env: Option<~[(~str, ~str)]>,
|
||||
dir: Option<&Path>,
|
||||
in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult {
|
||||
use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
|
||||
use libc::funcs::bsd44::getdtablesize;
|
||||
use std::libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp};
|
||||
use std::libc::funcs::bsd44::getdtablesize;
|
||||
|
||||
mod rustrt {
|
||||
extern {
|
||||
|
@ -433,7 +432,7 @@ fn spawn_process_os(prog: &str, args: &[~str],
|
|||
|
||||
#[cfg(unix)]
|
||||
fn with_argv<T>(prog: &str, args: &[~str], cb: |**libc::c_char| -> T) -> T {
|
||||
use vec;
|
||||
use std::vec;
|
||||
|
||||
// We can't directly convert `str`s into `*char`s, as someone needs to hold
|
||||
// a reference to the intermediary byte buffers. So first build an array to
|
||||
|
@ -459,7 +458,7 @@ fn with_argv<T>(prog: &str, args: &[~str], cb: |**libc::c_char| -> T) -> T {
|
|||
|
||||
#[cfg(unix)]
|
||||
fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*c_void| -> T) -> T {
|
||||
use vec;
|
||||
use std::vec;
|
||||
|
||||
// On posixy systems we can pass a char** for envp, which is a
|
||||
// null-terminated array of "k=v\n" strings. Like `with_argv`, we have to
|
||||
|
@ -540,8 +539,8 @@ fn waitpid(pid: pid_t) -> int {
|
|||
|
||||
#[cfg(windows)]
|
||||
fn waitpid_os(pid: pid_t) -> int {
|
||||
use libc::types::os::arch::extra::DWORD;
|
||||
use libc::consts::os::extra::{
|
||||
use std::libc::types::os::arch::extra::DWORD;
|
||||
use std::libc::consts::os::extra::{
|
||||
SYNCHRONIZE,
|
||||
PROCESS_QUERY_INFORMATION,
|
||||
FALSE,
|
||||
|
@ -549,7 +548,7 @@ fn waitpid(pid: pid_t) -> int {
|
|||
INFINITE,
|
||||
WAIT_FAILED
|
||||
};
|
||||
use libc::funcs::extra::kernel32::{
|
||||
use std::libc::funcs::extra::kernel32::{
|
||||
OpenProcess,
|
||||
GetExitCodeProcess,
|
||||
CloseHandle,
|
||||
|
@ -585,7 +584,7 @@ fn waitpid(pid: pid_t) -> int {
|
|||
|
||||
#[cfg(unix)]
|
||||
fn waitpid_os(pid: pid_t) -> int {
|
||||
use libc::funcs::posix01::wait::*;
|
||||
use std::libc::funcs::posix01::wait;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(target_os = "android")]
|
||||
|
@ -612,7 +611,7 @@ fn waitpid(pid: pid_t) -> int {
|
|||
}
|
||||
|
||||
let mut status = 0 as c_int;
|
||||
if unsafe { waitpid(pid, &mut status, 0) } == -1 {
|
||||
if unsafe { wait::waitpid(pid, &mut status, 0) } == -1 {
|
||||
fail!("failure in waitpid: {}", os::last_os_error());
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! The native runtime crate
|
||||
//!
|
||||
//! This crate contains an implementation of 1:1 scheduling for a "native"
|
||||
//! runtime. In addition, all I/O provided by this crate is the thread blocking
|
||||
//! version of I/O.
|
||||
|
||||
#[pkgid = "native#0.9-pre"];
|
||||
#[crate_id = "native#0.9-pre"];
|
||||
#[license = "MIT/ASL2"];
|
||||
#[crate_type = "rlib"];
|
||||
#[crate_type = "dylib"];
|
||||
|
||||
// Allow check-stage0-native for now
|
||||
#[cfg(stage0, test)] extern mod green;
|
||||
|
||||
// NB this crate explicitly does *not* allow glob imports, please seriously
|
||||
// consider whether they're needed before adding that feature here (the
|
||||
// answer is that you don't need them)
|
||||
|
||||
use std::os;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::task::Task;
|
||||
use std::rt;
|
||||
|
||||
pub mod io;
|
||||
pub mod task;
|
||||
|
||||
|
||||
// XXX: this should not exist here
|
||||
#[cfg(stage0)]
|
||||
#[lang = "start"]
|
||||
pub fn lang_start(main: *u8, argc: int, argv: **u8) -> int {
|
||||
use std::cast;
|
||||
use std::task;
|
||||
|
||||
do start(argc, argv) {
|
||||
// Instead of invoking main directly on this thread, invoke it on
|
||||
// another spawned thread that we are guaranteed to know the size of the
|
||||
// stack of. Currently, we do not have a method of figuring out the size
|
||||
// of the main thread's stack, so for stack overflow detection to work
|
||||
// we must spawn the task in a subtask which we know the stack size of.
|
||||
let main: extern "Rust" fn() = unsafe { cast::transmute(main) };
|
||||
let mut task = task::task();
|
||||
task.name("<main>");
|
||||
match do task.try { main() } {
|
||||
Ok(()) => { os::set_exit_status(0); }
|
||||
Err(..) => { os::set_exit_status(rt::DEFAULT_ERROR_CODE); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the given procedure after initializing the runtime with the given
|
||||
/// argc/argv.
|
||||
///
|
||||
/// This procedure is guaranteed to run on the thread calling this function, but
|
||||
/// the stack bounds for this rust task will *not* be set. Care must be taken
|
||||
/// for this function to not overflow its stack.
|
||||
///
|
||||
/// This function will only return once *all* native threads in the system have
|
||||
/// exited.
|
||||
pub fn start(argc: int, argv: **u8, main: proc()) -> int {
|
||||
rt::init(argc, argv);
|
||||
let mut exit_code = None;
|
||||
let mut main = Some(main);
|
||||
task::new().run(|| {
|
||||
exit_code = Some(run(main.take_unwrap()));
|
||||
});
|
||||
unsafe { rt::cleanup(); }
|
||||
return exit_code.unwrap();
|
||||
}
|
||||
|
||||
/// Executes a procedure on the current thread in a Rust task context.
|
||||
///
|
||||
/// This function has all of the same details as `start` except for a different
|
||||
/// number of arguments.
|
||||
pub fn run(main: proc()) -> int {
|
||||
// Run the main procedure and then wait for everything to finish
|
||||
main();
|
||||
unsafe {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().wait_for_other_tasks();
|
||||
}
|
||||
os::get_exit_status()
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Tasks implemented on top of OS threads
|
||||
//!
|
||||
//! This module contains the implementation of the 1:1 threading module required
|
||||
//! by rust tasks. This implements the necessary API traits laid out by std::rt
|
||||
//! in order to spawn new tasks and deschedule the current task.
|
||||
|
||||
use std::cast;
|
||||
use std::rt::env;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio;
|
||||
use std::rt::task::{Task, BlockedTask};
|
||||
use std::rt::thread::Thread;
|
||||
use std::rt;
|
||||
use std::task::TaskOpts;
|
||||
use std::unstable::mutex::Mutex;
|
||||
use std::unstable::stack;
|
||||
|
||||
use io;
|
||||
use task;
|
||||
|
||||
/// Creates a new Task which is ready to execute as a 1:1 task.
|
||||
pub fn new() -> ~Task {
|
||||
let mut task = ~Task::new();
|
||||
task.put_runtime(~Ops {
|
||||
lock: unsafe { Mutex::new() },
|
||||
awoken: false,
|
||||
} as ~rt::Runtime);
|
||||
return task;
|
||||
}
|
||||
|
||||
/// Spawns a function with the default configuration
|
||||
pub fn spawn(f: proc()) {
|
||||
spawn_opts(TaskOpts::new(), f)
|
||||
}
|
||||
|
||||
/// Spawns a new task given the configuration options and a procedure to run
|
||||
/// inside the task.
|
||||
pub fn spawn_opts(opts: TaskOpts, f: proc()) {
|
||||
let TaskOpts {
|
||||
watched: _watched,
|
||||
notify_chan, name, stack_size
|
||||
} = opts;
|
||||
|
||||
let mut task = new();
|
||||
task.name = name;
|
||||
match notify_chan {
|
||||
Some(chan) => {
|
||||
let on_exit = proc(task_result) { chan.send(task_result) };
|
||||
task.death.on_exit = Some(on_exit);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let stack = stack_size.unwrap_or(env::min_stack());
|
||||
let task = task;
|
||||
|
||||
// Spawning a new OS thread guarantees that __morestack will never get
|
||||
// triggered, but we must manually set up the actual stack bounds once this
|
||||
// function starts executing. This raises the lower limit by a bit because
|
||||
// by the time that this function is executing we've already consumed at
|
||||
// least a little bit of stack (we don't know the exact byte address at
|
||||
// which our stack started).
|
||||
Thread::spawn_stack(stack, proc() {
|
||||
let something_around_the_top_of_the_stack = 1;
|
||||
let addr = &something_around_the_top_of_the_stack as *int;
|
||||
unsafe {
|
||||
let my_stack = addr as uint;
|
||||
stack::record_stack_bounds(my_stack - stack + 1024, my_stack);
|
||||
}
|
||||
|
||||
let mut f = Some(f);
|
||||
task.run(|| { f.take_unwrap()() });
|
||||
})
|
||||
}
|
||||
|
||||
// This structure is the glue between channels and the 1:1 scheduling mode. This
|
||||
// structure is allocated once per task.
|
||||
struct Ops {
|
||||
lock: Mutex, // native synchronization
|
||||
awoken: bool, // used to prevent spurious wakeups
|
||||
}
|
||||
|
||||
impl rt::Runtime for Ops {
|
||||
fn yield_now(~self, mut cur_task: ~Task) {
|
||||
// put the task back in TLS and then invoke the OS thread yield
|
||||
cur_task.put_runtime(self as ~rt::Runtime);
|
||||
Local::put(cur_task);
|
||||
Thread::yield_now();
|
||||
}
|
||||
|
||||
fn maybe_yield(~self, mut cur_task: ~Task) {
|
||||
// just put the task back in TLS, on OS threads we never need to
|
||||
// opportunistically yield b/c the OS will do that for us (preemption)
|
||||
cur_task.put_runtime(self as ~rt::Runtime);
|
||||
Local::put(cur_task);
|
||||
}
|
||||
|
||||
fn wrap(~self) -> ~Any {
|
||||
self as ~Any
|
||||
}
|
||||
|
||||
// This function gets a little interesting. There are a few safety and
|
||||
// ownership violations going on here, but this is all done in the name of
|
||||
// shared state. Additionally, all of the violations are protected with a
|
||||
// mutex, so in theory there are no races.
|
||||
//
|
||||
// The first thing we need to do is to get a pointer to the task's internal
|
||||
// mutex. This address will not be changing (because the task is allocated
|
||||
// on the heap). We must have this handle separately because the task will
|
||||
// have its ownership transferred to the given closure. We're guaranteed,
|
||||
// however, that this memory will remain valid because *this* is the current
|
||||
// task's execution thread.
|
||||
//
|
||||
// The next weird part is where ownership of the task actually goes. We
|
||||
// relinquish it to the `f` blocking function, but upon returning this
|
||||
// function needs to replace the task back in TLS. There is no communication
|
||||
// from the wakeup thread back to this thread about the task pointer, and
|
||||
// there's really no need to. In order to get around this, we cast the task
|
||||
// to a `uint` which is then used at the end of this function to cast back
|
||||
// to a `~Task` object. Naturally, this looks like it violates ownership
|
||||
// semantics in that there may be two `~Task` objects.
|
||||
//
|
||||
// The fun part is that the wakeup half of this implementation knows to
|
||||
// "forget" the task on the other end. This means that the awakening half of
|
||||
// things silently relinquishes ownership back to this thread, but not in a
|
||||
// way that the compiler can understand. The task's memory is always valid
|
||||
// for both tasks because these operations are all done inside of a mutex.
|
||||
//
|
||||
// You'll also find that if blocking fails (the `f` function hands the
|
||||
// BlockedTask back to us), we will `cast::forget` the handles. The
|
||||
// reasoning for this is the same logic as above in that the task silently
|
||||
// transfers ownership via the `uint`, not through normal compiler
|
||||
// semantics.
|
||||
//
|
||||
// On a mildly unrelated note, it should also be pointed out that OS
|
||||
// condition variables are susceptible to spurious wakeups, which we need to
|
||||
// be ready for. In order to accomodate for this fact, we have an extra
|
||||
// `awoken` field which indicates whether we were actually woken up via some
|
||||
// invocation of `reawaken`. This flag is only ever accessed inside the
|
||||
// lock, so there's no need to make it atomic.
|
||||
fn deschedule(mut ~self, times: uint, mut cur_task: ~Task,
|
||||
f: |BlockedTask| -> Result<(), BlockedTask>) {
|
||||
let me = &mut *self as *mut Ops;
|
||||
cur_task.put_runtime(self as ~rt::Runtime);
|
||||
|
||||
unsafe {
|
||||
let cur_task_dupe = *cast::transmute::<&~Task, &uint>(&cur_task);
|
||||
let task = BlockedTask::block(cur_task);
|
||||
|
||||
if times == 1 {
|
||||
(*me).lock.lock();
|
||||
(*me).awoken = false;
|
||||
match f(task) {
|
||||
Ok(()) => {
|
||||
while !(*me).awoken {
|
||||
(*me).lock.wait();
|
||||
}
|
||||
}
|
||||
Err(task) => { cast::forget(task.wake()); }
|
||||
}
|
||||
(*me).lock.unlock();
|
||||
} else {
|
||||
let mut iter = task.make_selectable(times);
|
||||
(*me).lock.lock();
|
||||
(*me).awoken = false;
|
||||
let success = iter.all(|task| {
|
||||
match f(task) {
|
||||
Ok(()) => true,
|
||||
Err(task) => {
|
||||
cast::forget(task.wake());
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
while success && !(*me).awoken {
|
||||
(*me).lock.wait();
|
||||
}
|
||||
(*me).lock.unlock();
|
||||
}
|
||||
// re-acquire ownership of the task
|
||||
cur_task = cast::transmute::<uint, ~Task>(cur_task_dupe);
|
||||
}
|
||||
|
||||
// put the task back in TLS, and everything is as it once was.
|
||||
Local::put(cur_task);
|
||||
}
|
||||
|
||||
// See the comments on `deschedule` for why the task is forgotten here, and
|
||||
// why it's valid to do so.
|
||||
fn reawaken(mut ~self, mut to_wake: ~Task, _can_resched: bool) {
|
||||
unsafe {
|
||||
let me = &mut *self as *mut Ops;
|
||||
to_wake.put_runtime(self as ~rt::Runtime);
|
||||
cast::forget(to_wake);
|
||||
(*me).lock.lock();
|
||||
(*me).awoken = true;
|
||||
(*me).lock.signal();
|
||||
(*me).lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_sibling(~self, mut cur_task: ~Task, opts: TaskOpts, f: proc()) {
|
||||
cur_task.put_runtime(self as ~rt::Runtime);
|
||||
Local::put(cur_task);
|
||||
|
||||
task::spawn_opts(opts, f);
|
||||
}
|
||||
|
||||
fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> {
|
||||
static mut io: io::IoFactory = io::IoFactory;
|
||||
// Unsafety is from accessing `io`, which is guaranteed to be safe
|
||||
// because you can't do anything usable with this statically initialized
|
||||
// unit struct.
|
||||
Some(unsafe { rtio::LocalIo::new(&mut io as &mut rtio::IoFactory) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Ops {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.lock.destroy() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rt::Runtime;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::task::Task;
|
||||
use std::task;
|
||||
use std::task::TaskOpts;
|
||||
use super::{spawn, spawn_opts, Ops};
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_fail() {
|
||||
let (p, c) = Chan::<()>::new();
|
||||
do spawn {
|
||||
let _c = c;
|
||||
fail!()
|
||||
}
|
||||
assert_eq!(p.recv_opt(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_opts() {
|
||||
let mut opts = TaskOpts::new();
|
||||
opts.name = Some(SendStrStatic("test"));
|
||||
opts.stack_size = Some(20 * 4096);
|
||||
let (p, c) = Chan::new();
|
||||
opts.notify_chan = Some(c);
|
||||
spawn_opts(opts, proc() {});
|
||||
assert!(p.recv().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_opts_fail() {
|
||||
let mut opts = TaskOpts::new();
|
||||
let (p, c) = Chan::new();
|
||||
opts.notify_chan = Some(c);
|
||||
spawn_opts(opts, proc() { fail!() });
|
||||
assert!(p.recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yield_test() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
10.times(task::deschedule);
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_children() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
let (p, c2) = Chan::new();
|
||||
do spawn {
|
||||
let (p, c3) = Chan::new();
|
||||
do spawn {
|
||||
c3.send(());
|
||||
}
|
||||
p.recv();
|
||||
c2.send(());
|
||||
}
|
||||
p.recv();
|
||||
c.send(());
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_inherits() {
|
||||
let (p, c) = Chan::new();
|
||||
do spawn {
|
||||
let c = c;
|
||||
do spawn {
|
||||
let mut task: ~Task = Local::take();
|
||||
match task.maybe_take_runtime::<Ops>() {
|
||||
Some(ops) => {
|
||||
task.put_runtime(ops as ~Runtime);
|
||||
}
|
||||
None => fail!(),
|
||||
}
|
||||
Local::put(task);
|
||||
c.send(());
|
||||
}
|
||||
}
|
||||
p.recv();
|
||||
}
|
||||
}
|
|
@ -333,6 +333,10 @@ pub mod write {
|
|||
}
|
||||
|
||||
unsafe fn configure_llvm(sess: Session) {
|
||||
use std::unstable::mutex::{MUTEX_INIT, Mutex};
|
||||
static mut LOCK: Mutex = MUTEX_INIT;
|
||||
static mut CONFIGURED: bool = false;
|
||||
|
||||
// Copy what clan does by turning on loop vectorization at O2 and
|
||||
// slp vectorization at O3
|
||||
let vectorize_loop = !sess.no_vectorize_loops() &&
|
||||
|
@ -360,7 +364,13 @@ pub mod write {
|
|||
add(*arg);
|
||||
}
|
||||
|
||||
llvm::LLVMRustSetLLVMOptions(llvm_args.len() as c_int, llvm_args.as_ptr());
|
||||
LOCK.lock();
|
||||
if !CONFIGURED {
|
||||
llvm::LLVMRustSetLLVMOptions(llvm_args.len() as c_int,
|
||||
llvm_args.as_ptr());
|
||||
CONFIGURED = true;
|
||||
}
|
||||
LOCK.unlock();
|
||||
}
|
||||
|
||||
unsafe fn populate_llvm_passes(fpm: lib::llvm::PassManagerRef,
|
||||
|
|
|
@ -70,6 +70,15 @@ impl fold::ast_fold for StandardLibraryInjector {
|
|||
}];
|
||||
|
||||
if use_uv(&crate) && !*self.sess.building_library {
|
||||
vis.push(ast::view_item {
|
||||
node: ast::view_item_extern_mod(self.sess.ident_of("green"),
|
||||
None,
|
||||
~[vers_item],
|
||||
ast::DUMMY_NODE_ID),
|
||||
attrs: ~[],
|
||||
vis: ast::private,
|
||||
span: dummy_sp()
|
||||
});
|
||||
vis.push(ast::view_item {
|
||||
node: ast::view_item_extern_mod(self.sess.ident_of("rustuv"),
|
||||
None,
|
||||
|
|
|
@ -146,7 +146,7 @@ use std::hashmap::HashMap;
|
|||
use std::hashmap::HashSet;
|
||||
use std::libc::{c_uint, c_ulonglong, c_longlong};
|
||||
use std::ptr;
|
||||
use std::unstable::atomics;
|
||||
use std::sync::atomics;
|
||||
use std::vec;
|
||||
use syntax::codemap::{Span, Pos};
|
||||
use syntax::{ast, codemap, ast_util, ast_map, opt_vec};
|
||||
|
|
|
@ -390,7 +390,7 @@ pub fn mk_output_path(what: OutputType, where: Target,
|
|||
Bench => "bench",
|
||||
_ => ""
|
||||
},
|
||||
os::EXE_SUFFIX))
|
||||
os::consts::EXE_SUFFIX))
|
||||
};
|
||||
if !output_path.is_absolute() {
|
||||
output_path = os::getcwd().join(&output_path);
|
||||
|
|
|
@ -487,8 +487,9 @@ fn lib_output_file_name(workspace: &Path, short_name: &str) -> Path {
|
|||
}
|
||||
|
||||
fn output_file_name(workspace: &Path, short_name: ~str) -> Path {
|
||||
target_build_dir(workspace).join(short_name.as_slice()).join(format!("{}{}", short_name,
|
||||
os::EXE_SUFFIX))
|
||||
target_build_dir(workspace).join(short_name.as_slice())
|
||||
.join(format!("{}{}", short_name,
|
||||
os::consts::EXE_SUFFIX))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -1353,7 +1354,7 @@ fn test_import_rustpkg() {
|
|||
command_line_test([~"build", ~"foo"], workspace);
|
||||
debug!("workspace = {}", workspace.display());
|
||||
assert!(target_build_dir(workspace).join("foo").join(format!("pkg{}",
|
||||
os::EXE_SUFFIX)).exists());
|
||||
os::consts::EXE_SUFFIX)).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1366,7 +1367,7 @@ fn test_macro_pkg_script() {
|
|||
command_line_test([~"build", ~"foo"], workspace);
|
||||
debug!("workspace = {}", workspace.display());
|
||||
assert!(target_build_dir(workspace).join("foo").join(format!("pkg{}",
|
||||
os::EXE_SUFFIX)).exists());
|
||||
os::consts::EXE_SUFFIX)).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -11,12 +11,10 @@
|
|||
use ai = std::io::net::addrinfo;
|
||||
use std::libc::c_int;
|
||||
use std::ptr::null;
|
||||
use std::rt::BlockedTask;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::sched::Scheduler;
|
||||
use std::rt::task::BlockedTask;
|
||||
|
||||
use net;
|
||||
use super::{Loop, UvError, Request, wait_until_woken_after};
|
||||
use super::{Loop, UvError, Request, wait_until_woken_after, wakeup};
|
||||
use uvll;
|
||||
|
||||
struct Addrinfo {
|
||||
|
@ -108,8 +106,7 @@ impl GetAddrInfoRequest {
|
|||
cx.status = status;
|
||||
cx.addrinfo = Some(Addrinfo { handle: res });
|
||||
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(cx.slot.take_unwrap());
|
||||
wakeup(&mut cx.slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,12 +185,13 @@ pub fn accum_addrinfo(addr: &Addrinfo) -> ~[ai::Info] {
|
|||
#[cfg(test, not(target_os="android"))]
|
||||
mod test {
|
||||
use std::io::net::ip::{SocketAddr, Ipv4Addr};
|
||||
use super::*;
|
||||
use super::super::local_loop;
|
||||
use super::GetAddrInfoRequest;
|
||||
|
||||
#[test]
|
||||
fn getaddrinfo_test() {
|
||||
match GetAddrInfoRequest::run(local_loop(), Some("localhost"), None, None) {
|
||||
let loop_ = &mut local_loop().loop_;
|
||||
match GetAddrInfoRequest::run(loop_, Some("localhost"), None, None) {
|
||||
Ok(infos) => {
|
||||
let mut found_local = false;
|
||||
let local_addr = &SocketAddr {
|
||||
|
@ -211,9 +209,10 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn issue_10663() {
|
||||
let loop_ = &mut local_loop().loop_;
|
||||
// Something should happen here, but this certainly shouldn't cause
|
||||
// everything to die. The actual outcome we don't care too much about.
|
||||
GetAddrInfoRequest::run(local_loop(), Some("irc.n0v4.com"), None,
|
||||
GetAddrInfoRequest::run(loop_, Some("irc.n0v4.com"), None,
|
||||
None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,16 +127,15 @@ impl Drop for AsyncWatcher {
|
|||
mod test_remote {
|
||||
use std::rt::rtio::Callback;
|
||||
use std::rt::thread::Thread;
|
||||
use std::rt::tube::Tube;
|
||||
|
||||
use super::*;
|
||||
use super::AsyncWatcher;
|
||||
use super::super::local_loop;
|
||||
|
||||
// Make sure that we can fire watchers in remote threads and that they
|
||||
// actually trigger what they say they will.
|
||||
#[test]
|
||||
fn smoke_test() {
|
||||
struct MyCallback(Option<Tube<int>>);
|
||||
struct MyCallback(Option<Chan<int>>);
|
||||
impl Callback for MyCallback {
|
||||
fn call(&mut self) {
|
||||
// this can get called more than once, but we only want to send
|
||||
|
@ -147,16 +146,17 @@ mod test_remote {
|
|||
}
|
||||
}
|
||||
|
||||
let mut tube = Tube::new();
|
||||
let cb = ~MyCallback(Some(tube.clone()));
|
||||
let watcher = AsyncWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (port, chan) = Chan::new();
|
||||
let cb = ~MyCallback(Some(chan));
|
||||
let watcher = AsyncWatcher::new(&mut local_loop().loop_,
|
||||
cb as ~Callback);
|
||||
|
||||
let thread = do Thread::start {
|
||||
let mut watcher = watcher;
|
||||
watcher.fire();
|
||||
};
|
||||
|
||||
assert_eq!(tube.recv(), 1);
|
||||
assert_eq!(port.recv(), 1);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,14 @@ use std::cast::transmute;
|
|||
use std::cast;
|
||||
use std::libc::{c_int, c_char, c_void, size_t};
|
||||
use std::libc;
|
||||
use std::rt::BlockedTask;
|
||||
use std::rt::task::BlockedTask;
|
||||
use std::io::{FileStat, IoError};
|
||||
use std::io;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
|
||||
use super::{Loop, UvError, uv_error_to_io_error, wait_until_woken_after};
|
||||
use uvio::HomingIO;
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use super::{Loop, UvError, uv_error_to_io_error, wait_until_woken_after, wakeup};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
|
||||
pub struct FsRequest {
|
||||
|
@ -34,19 +33,19 @@ pub struct FileWatcher {
|
|||
priv loop_: Loop,
|
||||
priv fd: c_int,
|
||||
priv close: rtio::CloseBehavior,
|
||||
priv home: SchedHandle,
|
||||
priv home: HomeHandle,
|
||||
}
|
||||
|
||||
impl FsRequest {
|
||||
pub fn open(loop_: &Loop, path: &CString, flags: int, mode: int)
|
||||
pub fn open(io: &mut UvIoFactory, path: &CString, flags: int, mode: int)
|
||||
-> Result<FileWatcher, UvError>
|
||||
{
|
||||
execute(|req, cb| unsafe {
|
||||
uvll::uv_fs_open(loop_.handle,
|
||||
uvll::uv_fs_open(io.uv_loop(),
|
||||
req, path.with_ref(|p| p), flags as c_int,
|
||||
mode as c_int, cb)
|
||||
}).map(|req|
|
||||
FileWatcher::new(*loop_, req.get_result() as c_int,
|
||||
FileWatcher::new(io, req.get_result() as c_int,
|
||||
rtio::CloseSynchronously)
|
||||
)
|
||||
}
|
||||
|
@ -320,8 +319,7 @@ fn execute(f: |*uvll::uv_fs_t, uvll::uv_fs_cb| -> c_int)
|
|||
let slot: &mut Option<BlockedTask> = unsafe {
|
||||
cast::transmute(uvll::get_data_for_req(req))
|
||||
};
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(slot.take_unwrap());
|
||||
wakeup(slot);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -331,16 +329,17 @@ fn execute_nop(f: |*uvll::uv_fs_t, uvll::uv_fs_cb| -> c_int)
|
|||
}
|
||||
|
||||
impl HomingIO for FileWatcher {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl FileWatcher {
|
||||
pub fn new(loop_: Loop, fd: c_int, close: rtio::CloseBehavior) -> FileWatcher {
|
||||
pub fn new(io: &mut UvIoFactory, fd: c_int,
|
||||
close: rtio::CloseBehavior) -> FileWatcher {
|
||||
FileWatcher {
|
||||
loop_: loop_,
|
||||
loop_: Loop::wrap(io.uv_loop()),
|
||||
fd: fd,
|
||||
close: close,
|
||||
home: get_handle_to_current_scheduler!()
|
||||
home: io.make_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,8 +447,11 @@ mod test {
|
|||
use std::io;
|
||||
use std::str;
|
||||
use std::vec;
|
||||
use super::*;
|
||||
use l = super::super::local_loop;
|
||||
use super::FsRequest;
|
||||
use super::super::Loop;
|
||||
use super::super::local_loop;
|
||||
|
||||
fn l() -> &mut Loop { &mut local_loop().loop_ }
|
||||
|
||||
#[test]
|
||||
fn file_test_full_simple_sync() {
|
||||
|
@ -460,7 +462,7 @@ mod test {
|
|||
|
||||
{
|
||||
// open/create
|
||||
let result = FsRequest::open(l(), &path_str.to_c_str(),
|
||||
let result = FsRequest::open(local_loop(), &path_str.to_c_str(),
|
||||
create_flags as int, mode as int);
|
||||
assert!(result.is_ok());
|
||||
let result = result.unwrap();
|
||||
|
@ -473,7 +475,7 @@ mod test {
|
|||
|
||||
{
|
||||
// re-open
|
||||
let result = FsRequest::open(l(), &path_str.to_c_str(),
|
||||
let result = FsRequest::open(local_loop(), &path_str.to_c_str(),
|
||||
read_flags as int, 0);
|
||||
assert!(result.is_ok());
|
||||
let result = result.unwrap();
|
||||
|
@ -500,7 +502,7 @@ mod test {
|
|||
let create_flags = (O_RDWR | O_CREAT) as int;
|
||||
let mode = (S_IWUSR | S_IRUSR) as int;
|
||||
|
||||
let result = FsRequest::open(l(), path, create_flags, mode);
|
||||
let result = FsRequest::open(local_loop(), path, create_flags, mode);
|
||||
assert!(result.is_ok());
|
||||
let file = result.unwrap();
|
||||
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Homing I/O implementation
|
||||
//!
|
||||
//! In libuv, whenever a handle is created on an I/O loop it is illegal to use
|
||||
//! that handle outside of that I/O loop. We use libuv I/O with our green
|
||||
//! scheduler, and each green scheduler corresponds to a different I/O loop on a
|
||||
//! different OS thread. Green tasks are also free to roam among schedulers,
|
||||
//! which implies that it is possible to create an I/O handle on one event loop
|
||||
//! and then attempt to use it on another.
|
||||
//!
|
||||
//! In order to solve this problem, this module implements the notion of a
|
||||
//! "homing operation" which will transplant a task from its currently running
|
||||
//! scheduler back onto the original I/O loop. This is accomplished entirely at
|
||||
//! the librustuv layer with very little cooperation from the scheduler (which
|
||||
//! we don't even know exists technically).
|
||||
//!
|
||||
//! These homing operations are completed by first realizing that we're on the
|
||||
//! wrong I/O loop, then descheduling ourselves, sending ourselves to the
|
||||
//! correct I/O loop, and then waking up the I/O loop in order to process its
|
||||
//! local queue of tasks which need to run.
|
||||
//!
|
||||
//! This enqueueing is done with a concurrent queue from libstd, and the
|
||||
//! signalling is achieved with an async handle.
|
||||
|
||||
#[allow(dead_code)];
|
||||
|
||||
use std::cast;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::LocalIo;
|
||||
use std::rt::task::{Task, BlockedTask};
|
||||
|
||||
use ForbidUnwind;
|
||||
use queue::{Queue, QueuePool};
|
||||
|
||||
/// A handle to a remote libuv event loop. This handle will keep the event loop
|
||||
/// alive while active in order to ensure that a homing operation can always be
|
||||
/// completed.
|
||||
///
|
||||
/// Handles are clone-able in order to derive new handles from existing handles
|
||||
/// (very useful for when accepting a socket from a server).
|
||||
pub struct HomeHandle {
|
||||
priv queue: Queue,
|
||||
priv id: uint,
|
||||
}
|
||||
|
||||
impl HomeHandle {
|
||||
pub fn new(id: uint, pool: &mut QueuePool) -> HomeHandle {
|
||||
HomeHandle { queue: pool.queue(), id: id }
|
||||
}
|
||||
|
||||
fn send(&mut self, task: BlockedTask) {
|
||||
self.queue.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for HomeHandle {
|
||||
fn clone(&self) -> HomeHandle {
|
||||
HomeHandle {
|
||||
queue: self.queue.clone(),
|
||||
id: self.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local_id() -> uint {
|
||||
let mut io = match LocalIo::borrow() {
|
||||
Some(io) => io, None => return 0,
|
||||
};
|
||||
let io = io.get();
|
||||
unsafe {
|
||||
let (_vtable, ptr): (uint, uint) = cast::transmute(io);
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HomingIO {
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle;
|
||||
|
||||
/// This function will move tasks to run on their home I/O scheduler. Note
|
||||
/// that this function does *not* pin the task to the I/O scheduler, but
|
||||
/// rather it simply moves it to running on the I/O scheduler.
|
||||
fn go_to_IO_home(&mut self) -> uint {
|
||||
let _f = ForbidUnwind::new("going home");
|
||||
|
||||
let cur_loop_id = local_id();
|
||||
let destination = self.home().id;
|
||||
|
||||
// Try at all costs to avoid the homing operation because it is quite
|
||||
// expensive. Hence, we only deschedule/send if we're not on the correct
|
||||
// event loop. If we're already on the home event loop, then we're good
|
||||
// to go (remember we have no preemption, so we're guaranteed to stay on
|
||||
// this event loop as long as we avoid the scheduler).
|
||||
if cur_loop_id != destination {
|
||||
let cur_task: ~Task = Local::take();
|
||||
cur_task.deschedule(1, |task| {
|
||||
self.home().send(task);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Once we wake up, assert that we're in the right location
|
||||
assert_eq!(local_id(), destination);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
/// Fires a single homing missile, returning another missile targeted back
|
||||
/// at the original home of this task. In other words, this function will
|
||||
/// move the local task to its I/O scheduler and then return an RAII wrapper
|
||||
/// which will return the task home.
|
||||
fn fire_homing_missile(&mut self) -> HomingMissile {
|
||||
HomingMissile { io_home: self.go_to_IO_home() }
|
||||
}
|
||||
}
|
||||
|
||||
/// After a homing operation has been completed, this will return the current
|
||||
/// task back to its appropriate home (if applicable). The field is used to
|
||||
/// assert that we are where we think we are.
|
||||
struct HomingMissile {
|
||||
priv io_home: uint,
|
||||
}
|
||||
|
||||
impl HomingMissile {
|
||||
/// Check at runtime that the task has *not* transplanted itself to a
|
||||
/// different I/O loop while executing.
|
||||
pub fn check(&self, msg: &'static str) {
|
||||
assert!(local_id() == self.io_home, "{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HomingMissile {
|
||||
fn drop(&mut self) {
|
||||
let _f = ForbidUnwind::new("leaving home");
|
||||
|
||||
// It would truly be a sad day if we had moved off the home I/O
|
||||
// scheduler while we were doing I/O.
|
||||
self.check("task moved away from the home scheduler");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use green::sched;
|
||||
use green::{SchedPool, PoolConfig};
|
||||
use std::rt::rtio::RtioUdpSocket;
|
||||
use std::io::test::next_test_ip4;
|
||||
use std::task::TaskOpts;
|
||||
|
||||
use net::UdpWatcher;
|
||||
use super::super::local_loop;
|
||||
|
||||
// On one thread, create a udp socket. Then send that socket to another
|
||||
// thread and destroy the socket on the remote thread. This should make sure
|
||||
// that homing kicks in for the socket to go back home to the original
|
||||
// thread, close itself, and then come back to the last thread.
|
||||
#[test]
|
||||
fn test_homing_closes_correctly() {
|
||||
let (port, chan) = Chan::new();
|
||||
let mut pool = SchedPool::new(PoolConfig {
|
||||
threads: 1,
|
||||
event_loop_factory: None,
|
||||
});
|
||||
|
||||
do pool.spawn(TaskOpts::new()) {
|
||||
let listener = UdpWatcher::bind(local_loop(), next_test_ip4());
|
||||
chan.send(listener.unwrap());
|
||||
}
|
||||
|
||||
let task = do pool.task(TaskOpts::new()) {
|
||||
port.recv();
|
||||
};
|
||||
pool.spawn_sched().send(sched::TaskFromFriend(task));
|
||||
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_homing_read() {
|
||||
let (port, chan) = Chan::new();
|
||||
let mut pool = SchedPool::new(PoolConfig {
|
||||
threads: 1,
|
||||
event_loop_factory: None,
|
||||
});
|
||||
|
||||
do pool.spawn(TaskOpts::new()) {
|
||||
let addr1 = next_test_ip4();
|
||||
let addr2 = next_test_ip4();
|
||||
let listener = UdpWatcher::bind(local_loop(), addr2);
|
||||
chan.send((listener.unwrap(), addr1));
|
||||
let mut listener = UdpWatcher::bind(local_loop(), addr1).unwrap();
|
||||
listener.sendto([1, 2, 3, 4], addr2);
|
||||
}
|
||||
|
||||
let task = do pool.task(TaskOpts::new()) {
|
||||
let (mut watcher, addr) = port.recv();
|
||||
let mut buf = [0, ..10];
|
||||
assert_eq!(watcher.recvfrom(buf).unwrap(), (4, addr));
|
||||
};
|
||||
pool.spawn_sched().send(sched::TaskFromFriend(task));
|
||||
|
||||
pool.shutdown();
|
||||
}
|
||||
}
|
|
@ -97,72 +97,102 @@ impl Drop for IdleWatcher {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::rt::tube::Tube;
|
||||
use std::cast;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::rt::rtio::{Callback, PausableIdleCallback};
|
||||
use std::rt::task::{BlockedTask, Task};
|
||||
use std::rt::local::Local;
|
||||
use super::IdleWatcher;
|
||||
use super::super::local_loop;
|
||||
|
||||
struct MyCallback(Tube<int>, int);
|
||||
type Chan = Rc<RefCell<(Option<BlockedTask>, uint)>>;
|
||||
|
||||
struct MyCallback(Rc<RefCell<(Option<BlockedTask>, uint)>>, uint);
|
||||
impl Callback for MyCallback {
|
||||
fn call(&mut self) {
|
||||
match *self {
|
||||
MyCallback(ref mut tube, val) => tube.send(val)
|
||||
let task = match *self {
|
||||
MyCallback(ref rc, n) => {
|
||||
let mut slot = rc.borrow().borrow_mut();
|
||||
match *slot.get() {
|
||||
(ref mut task, ref mut val) => {
|
||||
*val = n;
|
||||
task.take_unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
task.wake().map(|t| t.reawaken(true));
|
||||
}
|
||||
}
|
||||
|
||||
fn mk(v: uint) -> (~IdleWatcher, Chan) {
|
||||
let rc = Rc::from_send(RefCell::new((None, 0)));
|
||||
let cb = ~MyCallback(rc.clone(), v);
|
||||
let cb = cb as ~Callback:;
|
||||
let cb = unsafe { cast::transmute(cb) };
|
||||
(IdleWatcher::new(&mut local_loop().loop_, cb), rc)
|
||||
}
|
||||
|
||||
fn sleep(chan: &Chan) -> uint {
|
||||
let task: ~Task = Local::take();
|
||||
task.deschedule(1, |task| {
|
||||
let mut slot = chan.borrow().borrow_mut();
|
||||
match *slot.get() {
|
||||
(ref mut slot, _) => {
|
||||
assert!(slot.is_none());
|
||||
*slot = Some(task);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let slot = chan.borrow().borrow();
|
||||
match *slot.get() { (_, n) => n }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_used() {
|
||||
let cb = ~MyCallback(Tube::new(), 1);
|
||||
let _idle = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (_idle, _chan) = mk(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_test() {
|
||||
let mut tube = Tube::new();
|
||||
let cb = ~MyCallback(tube.clone(), 1);
|
||||
let mut idle = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (mut idle, chan) = mk(1);
|
||||
idle.resume();
|
||||
tube.recv();
|
||||
assert_eq!(sleep(&chan), 1);
|
||||
}
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_fail() {
|
||||
let tube = Tube::new();
|
||||
let cb = ~MyCallback(tube.clone(), 1);
|
||||
let mut idle = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (mut idle, _chan) = mk(1);
|
||||
idle.resume();
|
||||
fail!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fun_combinations_of_methods() {
|
||||
let mut tube = Tube::new();
|
||||
let cb = ~MyCallback(tube.clone(), 1);
|
||||
let mut idle = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (mut idle, chan) = mk(1);
|
||||
idle.resume();
|
||||
tube.recv();
|
||||
assert_eq!(sleep(&chan), 1);
|
||||
idle.pause();
|
||||
idle.resume();
|
||||
idle.resume();
|
||||
tube.recv();
|
||||
assert_eq!(sleep(&chan), 1);
|
||||
idle.pause();
|
||||
idle.pause();
|
||||
idle.resume();
|
||||
tube.recv();
|
||||
assert_eq!(sleep(&chan), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pause_pauses() {
|
||||
let mut tube = Tube::new();
|
||||
let cb = ~MyCallback(tube.clone(), 1);
|
||||
let mut idle1 = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let cb = ~MyCallback(tube.clone(), 2);
|
||||
let mut idle2 = IdleWatcher::new(local_loop(), cb as ~Callback);
|
||||
let (mut idle1, chan1) = mk(1);
|
||||
let (mut idle2, chan2) = mk(2);
|
||||
idle2.resume();
|
||||
assert_eq!(tube.recv(), 2);
|
||||
assert_eq!(sleep(&chan2), 2);
|
||||
idle2.pause();
|
||||
idle1.resume();
|
||||
assert_eq!(tube.recv(), 1);
|
||||
assert_eq!(sleep(&chan1), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,23 +41,23 @@ via `close` and `delete` methods.
|
|||
#[crate_type = "rlib"];
|
||||
#[crate_type = "dylib"];
|
||||
|
||||
#[feature(macro_rules, globs)];
|
||||
#[feature(macro_rules)];
|
||||
|
||||
#[cfg(test)] extern mod green;
|
||||
|
||||
use std::cast::transmute;
|
||||
use std::cast;
|
||||
use std::io;
|
||||
use std::io::IoError;
|
||||
use std::libc::{c_int, malloc};
|
||||
use std::ptr::null;
|
||||
use std::ptr;
|
||||
use std::rt::BlockedTask;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::sched::Scheduler;
|
||||
use std::rt::task::{BlockedTask, Task};
|
||||
use std::str::raw::from_c_str;
|
||||
use std::str;
|
||||
use std::task;
|
||||
use std::unstable::finally::Finally;
|
||||
|
||||
use std::io::IoError;
|
||||
|
||||
pub use self::async::AsyncWatcher;
|
||||
pub use self::file::{FsRequest, FileWatcher};
|
||||
pub use self::idle::IdleWatcher;
|
||||
|
@ -70,6 +70,9 @@ pub use self::tty::TtyWatcher;
|
|||
|
||||
mod macros;
|
||||
|
||||
mod queue;
|
||||
mod homing;
|
||||
|
||||
/// The implementation of `rtio` for libuv
|
||||
pub mod uvio;
|
||||
|
||||
|
@ -144,32 +147,29 @@ pub trait UvHandle<T> {
|
|||
uvll::free_handle(handle);
|
||||
if data == ptr::null() { return }
|
||||
let slot: &mut Option<BlockedTask> = cast::transmute(data);
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(slot.take_unwrap());
|
||||
wakeup(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForbidSwitch {
|
||||
msg: &'static str,
|
||||
sched: uint,
|
||||
priv msg: &'static str,
|
||||
priv io: uint,
|
||||
}
|
||||
|
||||
impl ForbidSwitch {
|
||||
fn new(s: &'static str) -> ForbidSwitch {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
ForbidSwitch {
|
||||
msg: s,
|
||||
sched: sched.get().sched_id(),
|
||||
io: homing::local_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ForbidSwitch {
|
||||
fn drop(&mut self) {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
assert!(self.sched == sched.get().sched_id(),
|
||||
assert!(self.io == homing::local_id(),
|
||||
"didnt want a scheduler switch: {}",
|
||||
self.msg);
|
||||
}
|
||||
|
@ -199,14 +199,20 @@ fn wait_until_woken_after(slot: *mut Option<BlockedTask>, f: ||) {
|
|||
let _f = ForbidUnwind::new("wait_until_woken_after");
|
||||
unsafe {
|
||||
assert!((*slot).is_none());
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|_, task| {
|
||||
f();
|
||||
let task: ~Task = Local::take();
|
||||
task.deschedule(1, |task| {
|
||||
*slot = Some(task);
|
||||
})
|
||||
f();
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wakeup(slot: &mut Option<BlockedTask>) {
|
||||
assert!(slot.is_some());
|
||||
slot.take_unwrap().wake().map(|t| t.reawaken(true));
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
handle: *uvll::uv_req_t,
|
||||
priv defused: bool,
|
||||
|
@ -325,28 +331,26 @@ fn error_smoke_test() {
|
|||
pub fn uv_error_to_io_error(uverr: UvError) -> IoError {
|
||||
unsafe {
|
||||
// Importing error constants
|
||||
use uvll::*;
|
||||
use std::io::*;
|
||||
|
||||
// uv error descriptions are static
|
||||
let c_desc = uvll::uv_strerror(*uverr);
|
||||
let desc = str::raw::c_str_to_static_slice(c_desc);
|
||||
|
||||
let kind = match *uverr {
|
||||
UNKNOWN => OtherIoError,
|
||||
OK => OtherIoError,
|
||||
EOF => EndOfFile,
|
||||
EACCES => PermissionDenied,
|
||||
ECONNREFUSED => ConnectionRefused,
|
||||
ECONNRESET => ConnectionReset,
|
||||
ENOENT => FileNotFound,
|
||||
ENOTCONN => NotConnected,
|
||||
EPIPE => BrokenPipe,
|
||||
ECONNABORTED => ConnectionAborted,
|
||||
uvll::UNKNOWN => io::OtherIoError,
|
||||
uvll::OK => io::OtherIoError,
|
||||
uvll::EOF => io::EndOfFile,
|
||||
uvll::EACCES => io::PermissionDenied,
|
||||
uvll::ECONNREFUSED => io::ConnectionRefused,
|
||||
uvll::ECONNRESET => io::ConnectionReset,
|
||||
uvll::ENOTCONN => io::NotConnected,
|
||||
uvll::ENOENT => io::FileNotFound,
|
||||
uvll::EPIPE => io::BrokenPipe,
|
||||
uvll::ECONNABORTED => io::ConnectionAborted,
|
||||
err => {
|
||||
uvdebug!("uverr.code {}", err as int);
|
||||
// XXX: Need to map remaining uv error types
|
||||
OtherIoError
|
||||
io::OtherIoError
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -387,15 +391,17 @@ pub fn slice_to_uv_buf(v: &[u8]) -> Buf {
|
|||
uvll::uv_buf_t { base: data, len: v.len() as uvll::uv_buf_len_t }
|
||||
}
|
||||
|
||||
// This function is full of lies!
|
||||
#[cfg(test)]
|
||||
fn local_loop() -> &'static mut Loop {
|
||||
fn local_loop() -> &'static mut uvio::UvIoFactory {
|
||||
unsafe {
|
||||
cast::transmute({
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
let mut io = task.get().local_io().unwrap();
|
||||
let (_vtable, uvio): (uint, &'static mut uvio::UvIoFactory) =
|
||||
cast::transmute(sched.get().event_loop.io().unwrap());
|
||||
cast::transmute(io.get());
|
||||
uvio
|
||||
}.uv_loop())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,18 +27,20 @@ macro_rules! uvdebug (
|
|||
})
|
||||
)
|
||||
|
||||
// get a handle for the current scheduler
|
||||
macro_rules! get_handle_to_current_scheduler(
|
||||
() => ({
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
sched.get().make_handle()
|
||||
})
|
||||
)
|
||||
|
||||
pub fn dumb_println(args: &fmt::Arguments) {
|
||||
use std::io::native::file::FileDesc;
|
||||
use std::io;
|
||||
use std::libc;
|
||||
let mut out = FileDesc::new(libc::STDERR_FILENO, false);
|
||||
fmt::writeln(&mut out as &mut io::Writer, args);
|
||||
|
||||
struct Stderr;
|
||||
impl io::Writer for Stderr {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
libc::write(libc::STDERR_FILENO,
|
||||
data.as_ptr() as *libc::c_void,
|
||||
data.len() as libc::size_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut w = Stderr;
|
||||
fmt::writeln(&mut w as &mut io::Writer, args);
|
||||
}
|
||||
|
|
|
@ -9,24 +9,22 @@
|
|||
// except according to those terms.
|
||||
|
||||
use std::cast;
|
||||
use std::libc;
|
||||
use std::libc::{size_t, ssize_t, c_int, c_void, c_uint, c_char};
|
||||
use std::ptr;
|
||||
use std::rt::BlockedTask;
|
||||
use std::io::IoError;
|
||||
use std::io::net::ip::{Ipv4Addr, Ipv6Addr, SocketAddr, IpAddr};
|
||||
use std::rt::local::Local;
|
||||
use std::libc::{size_t, ssize_t, c_int, c_void, c_uint, c_char};
|
||||
use std::libc;
|
||||
use std::ptr;
|
||||
use std::rt::rtio;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
use std::rt::tube::Tube;
|
||||
use std::rt::task::BlockedTask;
|
||||
use std::str;
|
||||
use std::vec;
|
||||
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use stream::StreamWatcher;
|
||||
use super::{Loop, Request, UvError, Buf, status_to_io_result,
|
||||
uv_error_to_io_error, UvHandle, slice_to_uv_buf,
|
||||
wait_until_woken_after};
|
||||
use uvio::HomingIO;
|
||||
wait_until_woken_after, wakeup};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
use uvll::sockaddr;
|
||||
|
||||
|
@ -88,21 +86,19 @@ pub fn sockaddr_to_socket_addr(addr: *sockaddr) -> SocketAddr {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_ip4_conversion() {
|
||||
use std::rt;
|
||||
let ip4 = rt::test::next_test_ip4();
|
||||
use std::io::net::ip::{SocketAddr, Ipv4Addr};
|
||||
let ip4 = SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 4824 };
|
||||
socket_addr_as_sockaddr(ip4, |addr| {
|
||||
assert_eq!(ip4, sockaddr_to_socket_addr(addr));
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_ip6_conversion() {
|
||||
use std::rt;
|
||||
let ip6 = rt::test::next_test_ip6();
|
||||
use std::io::net::ip::{SocketAddr, Ipv6Addr};
|
||||
let ip6 = SocketAddr { ip: Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1), port: 4824 };
|
||||
socket_addr_as_sockaddr(ip6, |addr| {
|
||||
assert_eq!(ip6, sockaddr_to_socket_addr(addr));
|
||||
})
|
||||
|
@ -145,42 +141,47 @@ fn socket_name(sk: SocketNameKind, handle: *c_void) -> Result<SocketAddr, IoErro
|
|||
pub struct TcpWatcher {
|
||||
handle: *uvll::uv_tcp_t,
|
||||
stream: StreamWatcher,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
}
|
||||
|
||||
pub struct TcpListener {
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
handle: *uvll::uv_pipe_t,
|
||||
priv closing_task: Option<BlockedTask>,
|
||||
priv outgoing: Tube<Result<~rtio::RtioTcpStream, IoError>>,
|
||||
priv outgoing: Chan<Result<~rtio::RtioTcpStream, IoError>>,
|
||||
priv incoming: Port<Result<~rtio::RtioTcpStream, IoError>>,
|
||||
}
|
||||
|
||||
pub struct TcpAcceptor {
|
||||
listener: ~TcpListener,
|
||||
priv incoming: Tube<Result<~rtio::RtioTcpStream, IoError>>,
|
||||
}
|
||||
|
||||
// TCP watchers (clients/streams)
|
||||
|
||||
impl TcpWatcher {
|
||||
pub fn new(loop_: &Loop) -> TcpWatcher {
|
||||
pub fn new(io: &mut UvIoFactory) -> TcpWatcher {
|
||||
let handle = io.make_handle();
|
||||
TcpWatcher::new_home(&io.loop_, handle)
|
||||
}
|
||||
|
||||
fn new_home(loop_: &Loop, home: HomeHandle) -> TcpWatcher {
|
||||
let handle = unsafe { uvll::malloc_handle(uvll::UV_TCP) };
|
||||
assert_eq!(unsafe {
|
||||
uvll::uv_tcp_init(loop_.handle, handle)
|
||||
}, 0);
|
||||
TcpWatcher {
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: home,
|
||||
handle: handle,
|
||||
stream: StreamWatcher::new(handle),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(loop_: &mut Loop, address: SocketAddr)
|
||||
pub fn connect(io: &mut UvIoFactory, address: SocketAddr)
|
||||
-> Result<TcpWatcher, UvError>
|
||||
{
|
||||
struct Ctx { status: c_int, task: Option<BlockedTask> }
|
||||
|
||||
let tcp = TcpWatcher::new(loop_);
|
||||
let tcp = TcpWatcher::new(io);
|
||||
let ret = socket_addr_as_sockaddr(address, |addr| {
|
||||
let mut req = Request::new(uvll::UV_CONNECT);
|
||||
let result = unsafe {
|
||||
|
@ -213,14 +214,13 @@ impl TcpWatcher {
|
|||
assert!(status != uvll::ECANCELED);
|
||||
let cx: &mut Ctx = unsafe { req.get_data() };
|
||||
cx.status = status;
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
scheduler.resume_blocked_task_immediately(cx.task.take_unwrap());
|
||||
wakeup(&mut cx.task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HomingIO for TcpWatcher {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl rtio::RtioSocket for TcpWatcher {
|
||||
|
@ -290,17 +290,19 @@ impl Drop for TcpWatcher {
|
|||
// TCP listeners (unbound servers)
|
||||
|
||||
impl TcpListener {
|
||||
pub fn bind(loop_: &mut Loop, address: SocketAddr)
|
||||
pub fn bind(io: &mut UvIoFactory, address: SocketAddr)
|
||||
-> Result<~TcpListener, UvError> {
|
||||
let handle = unsafe { uvll::malloc_handle(uvll::UV_TCP) };
|
||||
assert_eq!(unsafe {
|
||||
uvll::uv_tcp_init(loop_.handle, handle)
|
||||
uvll::uv_tcp_init(io.uv_loop(), handle)
|
||||
}, 0);
|
||||
let (port, chan) = Chan::new();
|
||||
let l = ~TcpListener {
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
handle: handle,
|
||||
closing_task: None,
|
||||
outgoing: Tube::new(),
|
||||
outgoing: chan,
|
||||
incoming: port,
|
||||
};
|
||||
let res = socket_addr_as_sockaddr(address, |addr| unsafe {
|
||||
uvll::uv_tcp_bind(l.handle, addr)
|
||||
|
@ -313,7 +315,7 @@ impl TcpListener {
|
|||
}
|
||||
|
||||
impl HomingIO for TcpListener {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_tcp_t> for TcpListener {
|
||||
|
@ -330,11 +332,7 @@ impl rtio::RtioSocket for TcpListener {
|
|||
impl rtio::RtioTcpListener for TcpListener {
|
||||
fn listen(mut ~self) -> Result<~rtio::RtioTcpAcceptor, IoError> {
|
||||
// create the acceptor object from ourselves
|
||||
let incoming = self.outgoing.clone();
|
||||
let mut acceptor = ~TcpAcceptor {
|
||||
listener: self,
|
||||
incoming: incoming,
|
||||
};
|
||||
let mut acceptor = ~TcpAcceptor { listener: self };
|
||||
|
||||
let _m = acceptor.fire_homing_missile();
|
||||
// XXX: the 128 backlog should be configurable
|
||||
|
@ -347,19 +345,18 @@ impl rtio::RtioTcpListener for TcpListener {
|
|||
|
||||
extern fn listen_cb(server: *uvll::uv_stream_t, status: c_int) {
|
||||
assert!(status != uvll::ECANCELED);
|
||||
let tcp: &mut TcpListener = unsafe { UvHandle::from_uv_handle(&server) };
|
||||
let msg = match status {
|
||||
0 => {
|
||||
let loop_ = Loop::wrap(unsafe {
|
||||
uvll::get_loop_for_uv_handle(server)
|
||||
});
|
||||
let client = TcpWatcher::new(&loop_);
|
||||
let client = TcpWatcher::new_home(&loop_, tcp.home().clone());
|
||||
assert_eq!(unsafe { uvll::uv_accept(server, client.handle) }, 0);
|
||||
Ok(~client as ~rtio::RtioTcpStream)
|
||||
}
|
||||
n => Err(uv_error_to_io_error(UvError(n)))
|
||||
};
|
||||
|
||||
let tcp: &mut TcpListener = unsafe { UvHandle::from_uv_handle(&server) };
|
||||
tcp.outgoing.send(msg);
|
||||
}
|
||||
|
||||
|
@ -373,7 +370,7 @@ impl Drop for TcpListener {
|
|||
// TCP acceptors (bound servers)
|
||||
|
||||
impl HomingIO for TcpAcceptor {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { self.listener.home() }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { self.listener.home() }
|
||||
}
|
||||
|
||||
impl rtio::RtioSocket for TcpAcceptor {
|
||||
|
@ -385,8 +382,7 @@ impl rtio::RtioSocket for TcpAcceptor {
|
|||
|
||||
impl rtio::RtioTcpAcceptor for TcpAcceptor {
|
||||
fn accept(&mut self) -> Result<~rtio::RtioTcpStream, IoError> {
|
||||
let _m = self.fire_homing_missile();
|
||||
self.incoming.recv()
|
||||
self.listener.incoming.recv()
|
||||
}
|
||||
|
||||
fn accept_simultaneously(&mut self) -> Result<(), IoError> {
|
||||
|
@ -410,18 +406,18 @@ impl rtio::RtioTcpAcceptor for TcpAcceptor {
|
|||
|
||||
pub struct UdpWatcher {
|
||||
handle: *uvll::uv_udp_t,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
}
|
||||
|
||||
impl UdpWatcher {
|
||||
pub fn bind(loop_: &Loop, address: SocketAddr)
|
||||
pub fn bind(io: &mut UvIoFactory, address: SocketAddr)
|
||||
-> Result<UdpWatcher, UvError> {
|
||||
let udp = UdpWatcher {
|
||||
handle: unsafe { uvll::malloc_handle(uvll::UV_UDP) },
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
};
|
||||
assert_eq!(unsafe {
|
||||
uvll::uv_udp_init(loop_.handle, udp.handle)
|
||||
uvll::uv_udp_init(io.uv_loop(), udp.handle)
|
||||
}, 0);
|
||||
let result = socket_addr_as_sockaddr(address, |addr| unsafe {
|
||||
uvll::uv_udp_bind(udp.handle, addr, 0u32)
|
||||
|
@ -438,7 +434,7 @@ impl UvHandle<uvll::uv_udp_t> for UdpWatcher {
|
|||
}
|
||||
|
||||
impl HomingIO for UdpWatcher {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl rtio::RtioSocket for UdpWatcher {
|
||||
|
@ -519,9 +515,7 @@ impl rtio::RtioUdpSocket for UdpWatcher {
|
|||
Some(sockaddr_to_socket_addr(addr))
|
||||
};
|
||||
cx.result = Some((nread, addr));
|
||||
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(cx.task.take_unwrap());
|
||||
wakeup(&mut cx.task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -556,9 +550,7 @@ impl rtio::RtioUdpSocket for UdpWatcher {
|
|||
assert!(status != uvll::ECANCELED);
|
||||
let cx: &mut Ctx = unsafe { req.get_data() };
|
||||
cx.result = status;
|
||||
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(cx.task.take_unwrap());
|
||||
wakeup(&mut cx.task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,18 +632,13 @@ impl Drop for UdpWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// UV request support
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::rt::test::*;
|
||||
use std::rt::rtio::{RtioTcpStream, RtioTcpListener, RtioTcpAcceptor,
|
||||
RtioUdpSocket};
|
||||
use std::task;
|
||||
use std::io::test::{next_test_ip4, next_test_ip6};
|
||||
|
||||
use super::*;
|
||||
use super::{UdpWatcher, TcpWatcher, TcpListener};
|
||||
use super::super::local_loop;
|
||||
|
||||
#[test]
|
||||
|
@ -824,7 +811,6 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_read_read_read() {
|
||||
use std::rt::rtio::*;
|
||||
let addr = next_test_ip4();
|
||||
static MAX: uint = 5000;
|
||||
let (port, chan) = Chan::new();
|
||||
|
@ -843,7 +829,6 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
do spawn {
|
||||
port.recv();
|
||||
let mut stream = TcpWatcher::connect(local_loop(), addr).unwrap();
|
||||
let mut buf = [0, .. 2048];
|
||||
|
@ -857,7 +842,6 @@ mod test {
|
|||
}
|
||||
uvdebug!("read {} bytes total", total_bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#10102) server never sees second packet
|
||||
|
@ -922,7 +906,6 @@ mod test {
|
|||
assert!(total_bytes_sent >= MAX);
|
||||
}
|
||||
|
||||
do spawn {
|
||||
let l = local_loop();
|
||||
let mut client_out = UdpWatcher::bind(l, client_out_addr).unwrap();
|
||||
let mut client_in = UdpWatcher::bind(l, client_in_addr).unwrap();
|
||||
|
@ -947,14 +930,23 @@ mod test {
|
|||
// tell the server we're done
|
||||
assert!(client_out.sendto([0], server_in_addr).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_and_block() {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
let (port, chan) = Chan::<Port<()>>::new();
|
||||
|
||||
do spawn {
|
||||
let port2 = port.recv();
|
||||
let mut stream = TcpWatcher::connect(local_loop(), addr).unwrap();
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
port2.recv();
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
port2.recv();
|
||||
}
|
||||
|
||||
let listener = TcpListener::bind(local_loop(), addr).unwrap();
|
||||
let mut acceptor = listener.listen().unwrap();
|
||||
let (port2, chan2) = Chan::new();
|
||||
|
@ -975,30 +967,18 @@ mod test {
|
|||
}
|
||||
reads += 1;
|
||||
|
||||
chan2.send(());
|
||||
chan2.try_send(());
|
||||
}
|
||||
|
||||
// Make sure we had multiple reads
|
||||
assert!(reads > 1);
|
||||
}
|
||||
|
||||
do spawn {
|
||||
let port2 = port.recv();
|
||||
let mut stream = TcpWatcher::connect(local_loop(), addr).unwrap();
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
port2.recv();
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
stream.write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
port2.recv();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_tcp_server_and_client_on_diff_threads() {
|
||||
let addr = next_test_ip4();
|
||||
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
let listener = TcpListener::bind(local_loop(), addr).unwrap();
|
||||
let mut acceptor = listener.listen().unwrap();
|
||||
let mut stream = acceptor.accept().unwrap();
|
||||
|
@ -1010,132 +990,12 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let mut stream = TcpWatcher::connect(local_loop(), addr);
|
||||
while stream.is_err() {
|
||||
stream = TcpWatcher::connect(local_loop(), addr);
|
||||
}
|
||||
stream.unwrap().write([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
}
|
||||
}
|
||||
|
||||
// On one thread, create a udp socket. Then send that socket to another
|
||||
// thread and destroy the socket on the remote thread. This should make sure
|
||||
// that homing kicks in for the socket to go back home to the original
|
||||
// thread, close itself, and then come back to the last thread.
|
||||
#[test]
|
||||
fn test_homing_closes_correctly() {
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let listener = UdpWatcher::bind(local_loop(), next_test_ip4()).unwrap();
|
||||
chan.send(listener);
|
||||
}
|
||||
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
port.recv();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a bit of a crufty old test, but it has its uses.
|
||||
#[test]
|
||||
fn test_simple_homed_udp_io_bind_then_move_task_then_home_and_close() {
|
||||
use std::cast;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::{EventLoop, IoFactory};
|
||||
use std::rt::sched::Scheduler;
|
||||
use std::rt::sched::{Shutdown, TaskFromFriend};
|
||||
use std::rt::sleeper_list::SleeperList;
|
||||
use std::rt::task::Task;
|
||||
use std::rt::thread::Thread;
|
||||
use std::rt::deque::BufferPool;
|
||||
use std::task::TaskResult;
|
||||
use std::unstable::run_in_bare_thread;
|
||||
use uvio::UvEventLoop;
|
||||
|
||||
do run_in_bare_thread {
|
||||
let sleepers = SleeperList::new();
|
||||
let mut pool = BufferPool::new();
|
||||
let (worker1, stealer1) = pool.deque();
|
||||
let (worker2, stealer2) = pool.deque();
|
||||
let queues = ~[stealer1, stealer2];
|
||||
|
||||
let loop1 = ~UvEventLoop::new() as ~EventLoop;
|
||||
let mut sched1 = ~Scheduler::new(loop1, worker1, queues.clone(),
|
||||
sleepers.clone());
|
||||
let loop2 = ~UvEventLoop::new() as ~EventLoop;
|
||||
let mut sched2 = ~Scheduler::new(loop2, worker2, queues.clone(),
|
||||
sleepers.clone());
|
||||
|
||||
let handle1 = sched1.make_handle();
|
||||
let handle2 = sched2.make_handle();
|
||||
let tasksFriendHandle = sched2.make_handle();
|
||||
|
||||
let on_exit: proc(TaskResult) = proc(exit_status) {
|
||||
let mut handle1 = handle1;
|
||||
let mut handle2 = handle2;
|
||||
handle1.send(Shutdown);
|
||||
handle2.send(Shutdown);
|
||||
assert!(exit_status.is_ok());
|
||||
};
|
||||
|
||||
unsafe fn local_io() -> &'static mut IoFactory {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
let io = sched.get().event_loop.io();
|
||||
cast::transmute(io.unwrap())
|
||||
}
|
||||
|
||||
let test_function: proc() = proc() {
|
||||
let io = unsafe { local_io() };
|
||||
let addr = next_test_ip4();
|
||||
let maybe_socket = io.udp_bind(addr);
|
||||
// this socket is bound to this event loop
|
||||
assert!(maybe_socket.is_ok());
|
||||
|
||||
// block self on sched1
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
let mut tasksFriendHandle = Some(tasksFriendHandle);
|
||||
scheduler.deschedule_running_task_and_then(|_, task| {
|
||||
// unblock task
|
||||
task.wake().map(|task| {
|
||||
// send self to sched2
|
||||
tasksFriendHandle.take_unwrap()
|
||||
.send(TaskFromFriend(task));
|
||||
});
|
||||
// sched1 should now sleep since it has nothing else to do
|
||||
})
|
||||
// sched2 will wake up and get the task as we do nothing else,
|
||||
// the function ends and the socket goes out of scope sched2
|
||||
// will start to run the destructor the destructor will first
|
||||
// block the task, set it's home as sched1, then enqueue it
|
||||
// sched2 will dequeue the task, see that it has a home, and
|
||||
// send it to sched1 sched1 will wake up, exec the close
|
||||
// function on the correct loop, and then we're done
|
||||
};
|
||||
|
||||
let mut main_task = ~Task::new_root(&mut sched1.stack_pool, None,
|
||||
test_function);
|
||||
main_task.death.on_exit = Some(on_exit);
|
||||
|
||||
let null_task = ~do Task::new_root(&mut sched2.stack_pool, None) {
|
||||
// nothing
|
||||
};
|
||||
|
||||
let main_task = main_task;
|
||||
let sched1 = sched1;
|
||||
let thread1 = do Thread::start {
|
||||
sched1.bootstrap(main_task);
|
||||
};
|
||||
|
||||
let sched2 = sched2;
|
||||
let thread2 = do Thread::start {
|
||||
sched2.bootstrap(null_task);
|
||||
};
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
}
|
||||
|
||||
#[should_fail] #[test]
|
||||
fn tcp_listener_fail_cleanup() {
|
||||
|
@ -1176,7 +1036,7 @@ mod test {
|
|||
// force the handle to be created on a different scheduler, failure in
|
||||
// the original task will force a homing operation back to this
|
||||
// scheduler.
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
let w = UdpWatcher::bind(local_loop(), addr).unwrap();
|
||||
chan.send(w);
|
||||
}
|
||||
|
@ -1184,67 +1044,4 @@ mod test {
|
|||
let _w = port.recv();
|
||||
fail!();
|
||||
}
|
||||
|
||||
#[should_fail]
|
||||
#[test]
|
||||
#[ignore(reason = "linked failure")]
|
||||
fn linked_failure1() {
|
||||
let (port, chan) = Chan::new();
|
||||
let addr = next_test_ip4();
|
||||
|
||||
do spawn {
|
||||
let w = TcpListener::bind(local_loop(), addr).unwrap();
|
||||
let mut w = w.listen().unwrap();
|
||||
chan.send(());
|
||||
w.accept();
|
||||
}
|
||||
|
||||
port.recv();
|
||||
fail!();
|
||||
}
|
||||
|
||||
#[should_fail]
|
||||
#[test]
|
||||
#[ignore(reason = "linked failure")]
|
||||
fn linked_failure2() {
|
||||
let (port, chan) = Chan::new();
|
||||
let addr = next_test_ip4();
|
||||
|
||||
do spawn {
|
||||
let w = TcpListener::bind(local_loop(), addr).unwrap();
|
||||
let mut w = w.listen().unwrap();
|
||||
chan.send(());
|
||||
let mut buf = [0];
|
||||
w.accept().unwrap().read(buf);
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _w = TcpWatcher::connect(local_loop(), addr).unwrap();
|
||||
|
||||
fail!();
|
||||
}
|
||||
|
||||
#[should_fail]
|
||||
#[test]
|
||||
#[ignore(reason = "linked failure")]
|
||||
fn linked_failure3() {
|
||||
let (port, chan) = Chan::new();
|
||||
let addr = next_test_ip4();
|
||||
|
||||
do spawn {
|
||||
let chan = chan;
|
||||
let w = TcpListener::bind(local_loop(), addr).unwrap();
|
||||
let mut w = w.listen().unwrap();
|
||||
chan.send(());
|
||||
let mut conn = w.accept().unwrap();
|
||||
chan.send(());
|
||||
let buf = [0, ..65536];
|
||||
conn.write(buf);
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _w = TcpWatcher::connect(local_loop(), addr).unwrap();
|
||||
port.recv();
|
||||
fail!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,35 +9,33 @@
|
|||
// except according to those terms.
|
||||
|
||||
use std::c_str::CString;
|
||||
use std::libc;
|
||||
use std::rt::BlockedTask;
|
||||
use std::io::IoError;
|
||||
use std::rt::local::Local;
|
||||
use std::libc;
|
||||
use std::rt::rtio::{RtioPipe, RtioUnixListener, RtioUnixAcceptor};
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
use std::rt::tube::Tube;
|
||||
use std::rt::task::BlockedTask;
|
||||
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use stream::StreamWatcher;
|
||||
use super::{Loop, UvError, UvHandle, Request, uv_error_to_io_error,
|
||||
wait_until_woken_after};
|
||||
use uvio::HomingIO;
|
||||
wait_until_woken_after, wakeup};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
|
||||
pub struct PipeWatcher {
|
||||
stream: StreamWatcher,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
priv defused: bool,
|
||||
}
|
||||
|
||||
pub struct PipeListener {
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
pipe: *uvll::uv_pipe_t,
|
||||
priv outgoing: Tube<Result<~RtioPipe, IoError>>,
|
||||
priv outgoing: Chan<Result<~RtioPipe, IoError>>,
|
||||
priv incoming: Port<Result<~RtioPipe, IoError>>,
|
||||
}
|
||||
|
||||
pub struct PipeAcceptor {
|
||||
listener: ~PipeListener,
|
||||
priv incoming: Tube<Result<~RtioPipe, IoError>>,
|
||||
}
|
||||
|
||||
// PipeWatcher implementation and traits
|
||||
|
@ -46,7 +44,12 @@ impl PipeWatcher {
|
|||
// Creates an uninitialized pipe watcher. The underlying uv pipe is ready to
|
||||
// get bound to some other source (this is normally a helper method paired
|
||||
// with another call).
|
||||
pub fn new(loop_: &Loop, ipc: bool) -> PipeWatcher {
|
||||
pub fn new(io: &mut UvIoFactory, ipc: bool) -> PipeWatcher {
|
||||
let home = io.make_handle();
|
||||
PipeWatcher::new_home(&io.loop_, home, ipc)
|
||||
}
|
||||
|
||||
pub fn new_home(loop_: &Loop, home: HomeHandle, ipc: bool) -> PipeWatcher {
|
||||
let handle = unsafe {
|
||||
let handle = uvll::malloc_handle(uvll::UV_NAMED_PIPE);
|
||||
assert!(!handle.is_null());
|
||||
|
@ -56,26 +59,28 @@ impl PipeWatcher {
|
|||
};
|
||||
PipeWatcher {
|
||||
stream: StreamWatcher::new(handle),
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: home,
|
||||
defused: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open(loop_: &Loop, file: libc::c_int) -> Result<PipeWatcher, UvError>
|
||||
pub fn open(io: &mut UvIoFactory, file: libc::c_int)
|
||||
-> Result<PipeWatcher, UvError>
|
||||
{
|
||||
let pipe = PipeWatcher::new(loop_, false);
|
||||
let pipe = PipeWatcher::new(io, false);
|
||||
match unsafe { uvll::uv_pipe_open(pipe.handle(), file) } {
|
||||
0 => Ok(pipe),
|
||||
n => Err(UvError(n))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(loop_: &Loop, name: &CString) -> Result<PipeWatcher, UvError>
|
||||
pub fn connect(io: &mut UvIoFactory, name: &CString)
|
||||
-> Result<PipeWatcher, UvError>
|
||||
{
|
||||
struct Ctx { task: Option<BlockedTask>, result: libc::c_int, }
|
||||
let mut cx = Ctx { task: None, result: 0 };
|
||||
let mut req = Request::new(uvll::UV_CONNECT);
|
||||
let pipe = PipeWatcher::new(loop_, false);
|
||||
let pipe = PipeWatcher::new(io, false);
|
||||
|
||||
wait_until_woken_after(&mut cx.task, || {
|
||||
unsafe {
|
||||
|
@ -97,8 +102,7 @@ impl PipeWatcher {
|
|||
assert!(status != uvll::ECANCELED);
|
||||
let cx: &mut Ctx = unsafe { req.get_data() };
|
||||
cx.result = status;
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(cx.task.take_unwrap());
|
||||
wakeup(&mut cx.task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +129,7 @@ impl RtioPipe for PipeWatcher {
|
|||
}
|
||||
|
||||
impl HomingIO for PipeWatcher {
|
||||
fn home<'a>(&'a mut self) -> &'a mut SchedHandle { &mut self.home }
|
||||
fn home<'a>(&'a mut self) -> &'a mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_pipe_t> for PipeWatcher {
|
||||
|
@ -144,8 +148,10 @@ impl Drop for PipeWatcher {
|
|||
// PipeListener implementation and traits
|
||||
|
||||
impl PipeListener {
|
||||
pub fn bind(loop_: &Loop, name: &CString) -> Result<~PipeListener, UvError> {
|
||||
let pipe = PipeWatcher::new(loop_, false);
|
||||
pub fn bind(io: &mut UvIoFactory, name: &CString)
|
||||
-> Result<~PipeListener, UvError>
|
||||
{
|
||||
let pipe = PipeWatcher::new(io, false);
|
||||
match unsafe {
|
||||
uvll::uv_pipe_bind(pipe.handle(), name.with_ref(|p| p))
|
||||
} {
|
||||
|
@ -153,10 +159,12 @@ impl PipeListener {
|
|||
// If successful, unwrap the PipeWatcher because we control how
|
||||
// we close the pipe differently. We can't rely on
|
||||
// StreamWatcher's default close method.
|
||||
let (port, chan) = Chan::new();
|
||||
let p = ~PipeListener {
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
pipe: pipe.unwrap(),
|
||||
outgoing: Tube::new(),
|
||||
incoming: port,
|
||||
outgoing: chan,
|
||||
};
|
||||
Ok(p.install())
|
||||
}
|
||||
|
@ -168,11 +176,7 @@ impl PipeListener {
|
|||
impl RtioUnixListener for PipeListener {
|
||||
fn listen(mut ~self) -> Result<~RtioUnixAcceptor, IoError> {
|
||||
// create the acceptor object from ourselves
|
||||
let incoming = self.outgoing.clone();
|
||||
let mut acceptor = ~PipeAcceptor {
|
||||
listener: self,
|
||||
incoming: incoming,
|
||||
};
|
||||
let mut acceptor = ~PipeAcceptor { listener: self };
|
||||
|
||||
let _m = acceptor.fire_homing_missile();
|
||||
// XXX: the 128 backlog should be configurable
|
||||
|
@ -184,7 +188,7 @@ impl RtioUnixListener for PipeListener {
|
|||
}
|
||||
|
||||
impl HomingIO for PipeListener {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_pipe_t> for PipeListener {
|
||||
|
@ -193,19 +197,19 @@ impl UvHandle<uvll::uv_pipe_t> for PipeListener {
|
|||
|
||||
extern fn listen_cb(server: *uvll::uv_stream_t, status: libc::c_int) {
|
||||
assert!(status != uvll::ECANCELED);
|
||||
|
||||
let pipe: &mut PipeListener = unsafe { UvHandle::from_uv_handle(&server) };
|
||||
let msg = match status {
|
||||
0 => {
|
||||
let loop_ = Loop::wrap(unsafe {
|
||||
uvll::get_loop_for_uv_handle(server)
|
||||
});
|
||||
let client = PipeWatcher::new(&loop_, false);
|
||||
let client = PipeWatcher::new_home(&loop_, pipe.home().clone(), false);
|
||||
assert_eq!(unsafe { uvll::uv_accept(server, client.handle()) }, 0);
|
||||
Ok(~client as ~RtioPipe)
|
||||
}
|
||||
n => Err(uv_error_to_io_error(UvError(n)))
|
||||
};
|
||||
|
||||
let pipe: &mut PipeListener = unsafe { UvHandle::from_uv_handle(&server) };
|
||||
pipe.outgoing.send(msg);
|
||||
}
|
||||
|
||||
|
@ -220,21 +224,20 @@ impl Drop for PipeListener {
|
|||
|
||||
impl RtioUnixAcceptor for PipeAcceptor {
|
||||
fn accept(&mut self) -> Result<~RtioPipe, IoError> {
|
||||
let _m = self.fire_homing_missile();
|
||||
self.incoming.recv()
|
||||
self.listener.incoming.recv()
|
||||
}
|
||||
}
|
||||
|
||||
impl HomingIO for PipeAcceptor {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { self.listener.home() }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.listener.home }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rt::rtio::{RtioUnixListener, RtioUnixAcceptor, RtioPipe};
|
||||
use std::rt::test::next_test_unix;
|
||||
use std::io::test::next_test_unix;
|
||||
|
||||
use super::*;
|
||||
use super::{PipeWatcher, PipeListener};
|
||||
use super::super::local_loop;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -8,32 +8,31 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::io::IoError;
|
||||
use std::io::process;
|
||||
use std::libc::c_int;
|
||||
use std::libc;
|
||||
use std::ptr;
|
||||
use std::rt::BlockedTask;
|
||||
use std::io::IoError;
|
||||
use std::io::process::*;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::RtioProcess;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
use std::rt::task::BlockedTask;
|
||||
use std::vec;
|
||||
|
||||
use super::{Loop, UvHandle, UvError, uv_error_to_io_error,
|
||||
wait_until_woken_after};
|
||||
use uvio::HomingIO;
|
||||
use uvll;
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use pipe::PipeWatcher;
|
||||
use super::{UvHandle, UvError, uv_error_to_io_error,
|
||||
wait_until_woken_after, wakeup};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
|
||||
pub struct Process {
|
||||
handle: *uvll::uv_process_t,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
|
||||
/// Task to wake up (may be null) for when the process exits
|
||||
to_wake: Option<BlockedTask>,
|
||||
|
||||
/// Collected from the exit_cb
|
||||
exit_status: Option<ProcessExit>,
|
||||
exit_status: Option<process::ProcessExit>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
|
@ -41,7 +40,7 @@ impl Process {
|
|||
///
|
||||
/// Returns either the corresponding process object or an error which
|
||||
/// occurred.
|
||||
pub fn spawn(loop_: &Loop, config: ProcessConfig)
|
||||
pub fn spawn(io_loop: &mut UvIoFactory, config: process::ProcessConfig)
|
||||
-> Result<(~Process, ~[Option<PipeWatcher>]), UvError>
|
||||
{
|
||||
let cwd = config.cwd.map(|s| s.to_c_str());
|
||||
|
@ -52,7 +51,7 @@ impl Process {
|
|||
stdio.set_len(io.len());
|
||||
for (slot, other) in stdio.iter().zip(io.iter()) {
|
||||
let io = set_stdio(slot as *uvll::uv_stdio_container_t, other,
|
||||
loop_);
|
||||
io_loop);
|
||||
ret_io.push(io);
|
||||
}
|
||||
}
|
||||
|
@ -78,12 +77,12 @@ impl Process {
|
|||
let handle = UvHandle::alloc(None::<Process>, uvll::UV_PROCESS);
|
||||
let process = ~Process {
|
||||
handle: handle,
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io_loop.make_handle(),
|
||||
to_wake: None,
|
||||
exit_status: None,
|
||||
};
|
||||
match unsafe {
|
||||
uvll::uv_spawn(loop_.handle, handle, &options)
|
||||
uvll::uv_spawn(io_loop.uv_loop(), handle, &options)
|
||||
} {
|
||||
0 => Ok(process.install()),
|
||||
err => Err(UvError(err)),
|
||||
|
@ -105,33 +104,28 @@ extern fn on_exit(handle: *uvll::uv_process_t,
|
|||
|
||||
assert!(p.exit_status.is_none());
|
||||
p.exit_status = Some(match term_signal {
|
||||
0 => ExitStatus(exit_status as int),
|
||||
n => ExitSignal(n as int),
|
||||
0 => process::ExitStatus(exit_status as int),
|
||||
n => process::ExitSignal(n as int),
|
||||
});
|
||||
|
||||
match p.to_wake.take() {
|
||||
Some(task) => {
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
scheduler.resume_blocked_task_immediately(task);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if p.to_wake.is_none() { return }
|
||||
wakeup(&mut p.to_wake);
|
||||
}
|
||||
|
||||
unsafe fn set_stdio(dst: *uvll::uv_stdio_container_t,
|
||||
io: &StdioContainer,
|
||||
loop_: &Loop) -> Option<PipeWatcher> {
|
||||
io: &process::StdioContainer,
|
||||
io_loop: &mut UvIoFactory) -> Option<PipeWatcher> {
|
||||
match *io {
|
||||
Ignored => {
|
||||
process::Ignored => {
|
||||
uvll::set_stdio_container_flags(dst, uvll::STDIO_IGNORE);
|
||||
None
|
||||
}
|
||||
InheritFd(fd) => {
|
||||
process::InheritFd(fd) => {
|
||||
uvll::set_stdio_container_flags(dst, uvll::STDIO_INHERIT_FD);
|
||||
uvll::set_stdio_container_fd(dst, fd);
|
||||
None
|
||||
}
|
||||
CreatePipe(readable, writable) => {
|
||||
process::CreatePipe(readable, writable) => {
|
||||
let mut flags = uvll::STDIO_CREATE_PIPE as libc::c_int;
|
||||
if readable {
|
||||
flags |= uvll::STDIO_READABLE_PIPE as libc::c_int;
|
||||
|
@ -139,7 +133,7 @@ unsafe fn set_stdio(dst: *uvll::uv_stdio_container_t,
|
|||
if writable {
|
||||
flags |= uvll::STDIO_WRITABLE_PIPE as libc::c_int;
|
||||
}
|
||||
let pipe = PipeWatcher::new(loop_, false);
|
||||
let pipe = PipeWatcher::new(io_loop, false);
|
||||
uvll::set_stdio_container_flags(dst, flags);
|
||||
uvll::set_stdio_container_stream(dst, pipe.handle());
|
||||
Some(pipe)
|
||||
|
@ -186,7 +180,7 @@ fn with_env<T>(env: Option<&[(~str, ~str)]>, f: |**libc::c_char| -> T) -> T {
|
|||
}
|
||||
|
||||
impl HomingIO for Process {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_process_t> for Process {
|
||||
|
@ -208,7 +202,7 @@ impl RtioProcess for Process {
|
|||
}
|
||||
}
|
||||
|
||||
fn wait(&mut self) -> ProcessExit {
|
||||
fn wait(&mut self) -> process::ProcessExit {
|
||||
// Make sure (on the home scheduler) that we have an exit status listed
|
||||
let _m = self.fire_homing_missile();
|
||||
match self.exit_status {
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! A concurrent queue used to signal remote event loops
|
||||
//!
|
||||
//! This queue implementation is used to send tasks among event loops. This is
|
||||
//! backed by a multi-producer/single-consumer queue from libstd and uv_async_t
|
||||
//! handles (to wake up a remote event loop).
|
||||
//!
|
||||
//! The uv_async_t is stored next to the event loop, so in order to not keep the
|
||||
//! event loop alive we use uv_ref and uv_unref in order to control when the
|
||||
//! async handle is active or not.
|
||||
|
||||
#[allow(dead_code)];
|
||||
|
||||
use std::cast;
|
||||
use std::libc::{c_void, c_int};
|
||||
use std::rt::task::BlockedTask;
|
||||
use std::unstable::sync::LittleLock;
|
||||
use mpsc = std::sync::mpsc_queue;
|
||||
|
||||
use async::AsyncWatcher;
|
||||
use super::{Loop, UvHandle};
|
||||
use uvll;
|
||||
|
||||
enum Message {
|
||||
Task(BlockedTask),
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
struct State {
|
||||
handle: *uvll::uv_async_t,
|
||||
lock: LittleLock, // see comments in async_cb for why this is needed
|
||||
}
|
||||
|
||||
/// This structure is intended to be stored next to the event loop, and it is
|
||||
/// used to create new `Queue` structures.
|
||||
pub struct QueuePool {
|
||||
priv producer: mpsc::Producer<Message, State>,
|
||||
priv consumer: mpsc::Consumer<Message, State>,
|
||||
priv refcnt: uint,
|
||||
}
|
||||
|
||||
/// This type is used to send messages back to the original event loop.
|
||||
pub struct Queue {
|
||||
priv queue: mpsc::Producer<Message, State>,
|
||||
}
|
||||
|
||||
extern fn async_cb(handle: *uvll::uv_async_t, status: c_int) {
|
||||
assert_eq!(status, 0);
|
||||
let state: &mut QueuePool = unsafe {
|
||||
cast::transmute(uvll::get_data_for_uv_handle(handle))
|
||||
};
|
||||
let packet = unsafe { state.consumer.packet() };
|
||||
|
||||
// Remember that there is no guarantee about how many times an async
|
||||
// callback is called with relation to the number of sends, so process the
|
||||
// entire queue in a loop.
|
||||
loop {
|
||||
match state.consumer.pop() {
|
||||
mpsc::Data(Task(task)) => {
|
||||
task.wake().map(|t| t.reawaken(true));
|
||||
}
|
||||
mpsc::Data(Increment) => unsafe {
|
||||
if state.refcnt == 0 {
|
||||
uvll::uv_ref((*packet).handle);
|
||||
}
|
||||
state.refcnt += 1;
|
||||
},
|
||||
mpsc::Data(Decrement) => unsafe {
|
||||
state.refcnt -= 1;
|
||||
if state.refcnt == 0 {
|
||||
uvll::uv_unref((*packet).handle);
|
||||
}
|
||||
},
|
||||
mpsc::Empty | mpsc::Inconsistent => break
|
||||
};
|
||||
}
|
||||
|
||||
// If the refcount is now zero after processing the queue, then there is no
|
||||
// longer a reference on the async handle and it is possible that this event
|
||||
// loop can exit. What we're not guaranteed, however, is that a producer in
|
||||
// the middle of dropping itself is yet done with the handle. It could be
|
||||
// possible that we saw their Decrement message but they have yet to signal
|
||||
// on the async handle. If we were to return immediately, the entire uv loop
|
||||
// could be destroyed meaning the call to uv_async_send would abort()
|
||||
//
|
||||
// In order to fix this, an OS mutex is used to wait for the other end to
|
||||
// finish before we continue. The drop block on a handle will acquire a
|
||||
// mutex and then drop it after both the push and send have been completed.
|
||||
// If we acquire the mutex here, then we are guaranteed that there are no
|
||||
// longer any senders which are holding on to their handles, so we can
|
||||
// safely allow the event loop to exit.
|
||||
if state.refcnt == 0 {
|
||||
unsafe {
|
||||
let _l = (*packet).lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueuePool {
|
||||
pub fn new(loop_: &mut Loop) -> ~QueuePool {
|
||||
let handle = UvHandle::alloc(None::<AsyncWatcher>, uvll::UV_ASYNC);
|
||||
let (c, p) = mpsc::queue(State {
|
||||
handle: handle,
|
||||
lock: LittleLock::new(),
|
||||
});
|
||||
let q = ~QueuePool {
|
||||
producer: p,
|
||||
consumer: c,
|
||||
refcnt: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
assert_eq!(uvll::uv_async_init(loop_.handle, handle, async_cb), 0);
|
||||
uvll::uv_unref(handle);
|
||||
let data: *c_void = *cast::transmute::<&~QueuePool, &*c_void>(&q);
|
||||
uvll::set_data_for_uv_handle(handle, data);
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
pub fn queue(&mut self) -> Queue {
|
||||
unsafe {
|
||||
if self.refcnt == 0 {
|
||||
uvll::uv_ref((*self.producer.packet()).handle);
|
||||
}
|
||||
self.refcnt += 1;
|
||||
}
|
||||
Queue { queue: self.producer.clone() }
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> *uvll::uv_async_t {
|
||||
unsafe { (*self.producer.packet()).handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn push(&mut self, task: BlockedTask) {
|
||||
self.queue.push(Task(task));
|
||||
unsafe {
|
||||
uvll::uv_async_send((*self.queue.packet()).handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Queue {
|
||||
fn clone(&self) -> Queue {
|
||||
// Push a request to increment on the queue, but there's no need to
|
||||
// signal the event loop to process it at this time. We're guaranteed
|
||||
// that the count is at least one (because we have a queue right here),
|
||||
// and if the queue is dropped later on it'll see the increment for the
|
||||
// decrement anyway.
|
||||
unsafe {
|
||||
cast::transmute_mut(self).queue.push(Increment);
|
||||
}
|
||||
Queue { queue: self.queue.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Queue {
|
||||
fn drop(&mut self) {
|
||||
// See the comments in the async_cb function for why there is a lock
|
||||
// that is acquired only on a drop.
|
||||
unsafe {
|
||||
let state = self.queue.packet();
|
||||
let _l = (*state).lock.lock();
|
||||
self.queue.push(Decrement);
|
||||
uvll::uv_async_send((*state).handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
uvll::uv_close(self.handle, cast::transmute(0));
|
||||
// Note that this does *not* free the handle, that is the
|
||||
// responsibility of the caller because the uv loop must be closed
|
||||
// before we deallocate this uv handle.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,34 +10,33 @@
|
|||
|
||||
use std::libc::c_int;
|
||||
use std::io::signal::Signum;
|
||||
use std::rt::sched::{SchedHandle, Scheduler};
|
||||
use std::comm::SharedChan;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::RtioSignal;
|
||||
|
||||
use super::{Loop, UvError, UvHandle};
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use super::{UvError, UvHandle};
|
||||
use uvll;
|
||||
use uvio::HomingIO;
|
||||
use uvio::UvIoFactory;
|
||||
|
||||
pub struct SignalWatcher {
|
||||
handle: *uvll::uv_signal_t,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
|
||||
channel: SharedChan<Signum>,
|
||||
signal: Signum,
|
||||
}
|
||||
|
||||
impl SignalWatcher {
|
||||
pub fn new(loop_: &mut Loop, signum: Signum,
|
||||
pub fn new(io: &mut UvIoFactory, signum: Signum,
|
||||
channel: SharedChan<Signum>) -> Result<~SignalWatcher, UvError> {
|
||||
let s = ~SignalWatcher {
|
||||
handle: UvHandle::alloc(None::<SignalWatcher>, uvll::UV_SIGNAL),
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
channel: channel,
|
||||
signal: signum,
|
||||
};
|
||||
assert_eq!(unsafe {
|
||||
uvll::uv_signal_init(loop_.handle, s.handle)
|
||||
uvll::uv_signal_init(io.uv_loop(), s.handle)
|
||||
}, 0);
|
||||
|
||||
match unsafe {
|
||||
|
@ -53,11 +52,11 @@ impl SignalWatcher {
|
|||
extern fn signal_cb(handle: *uvll::uv_signal_t, signum: c_int) {
|
||||
let s: &mut SignalWatcher = unsafe { UvHandle::from_uv_handle(&handle) };
|
||||
assert_eq!(signum as int, s.signal as int);
|
||||
s.channel.send_deferred(s.signal);
|
||||
s.channel.try_send(s.signal);
|
||||
}
|
||||
|
||||
impl HomingIO for SignalWatcher {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_signal_t> for SignalWatcher {
|
||||
|
@ -69,15 +68,15 @@ impl RtioSignal for SignalWatcher {}
|
|||
impl Drop for SignalWatcher {
|
||||
fn drop(&mut self) {
|
||||
let _m = self.fire_homing_missile();
|
||||
self.close_async_();
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use super::super::local_loop;
|
||||
use std::io::signal;
|
||||
use super::SignalWatcher;
|
||||
|
||||
#[test]
|
||||
fn closing_channel_during_drop_doesnt_kill_everything() {
|
||||
|
|
|
@ -11,12 +11,10 @@
|
|||
use std::cast;
|
||||
use std::libc::{c_int, size_t, ssize_t};
|
||||
use std::ptr;
|
||||
use std::rt::BlockedTask;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::sched::Scheduler;
|
||||
use std::rt::task::BlockedTask;
|
||||
|
||||
use super::{UvError, Buf, slice_to_uv_buf, Request, wait_until_woken_after,
|
||||
ForbidUnwind};
|
||||
ForbidUnwind, wakeup};
|
||||
use uvll;
|
||||
|
||||
// This is a helper structure which is intended to get embedded into other
|
||||
|
@ -164,8 +162,7 @@ extern fn read_cb(handle: *uvll::uv_stream_t, nread: ssize_t, _buf: *Buf) {
|
|||
unsafe { assert_eq!(uvll::uv_read_stop(handle), 0); }
|
||||
rcx.result = nread;
|
||||
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
scheduler.resume_blocked_task_immediately(rcx.task.take_unwrap());
|
||||
wakeup(&mut rcx.task);
|
||||
}
|
||||
|
||||
// Unlike reading, the WriteContext is stored in the uv_write_t request. Like
|
||||
|
@ -180,6 +177,5 @@ extern fn write_cb(req: *uvll::uv_write_t, status: c_int) {
|
|||
wcx.result = status;
|
||||
req.defuse();
|
||||
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(wcx.task.take_unwrap());
|
||||
wakeup(&mut wcx.task);
|
||||
}
|
||||
|
|
|
@ -9,19 +9,19 @@
|
|||
// except according to those terms.
|
||||
|
||||
use std::libc::c_int;
|
||||
use std::rt::BlockedTask;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::RtioTimer;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
use std::rt::task::{BlockedTask, Task};
|
||||
use std::util;
|
||||
|
||||
use homing::{HomeHandle, HomingIO};
|
||||
use super::{UvHandle, ForbidUnwind, ForbidSwitch};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
use super::{Loop, UvHandle, ForbidUnwind, ForbidSwitch};
|
||||
use uvio::HomingIO;
|
||||
|
||||
pub struct TimerWatcher {
|
||||
handle: *uvll::uv_timer_t,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
action: Option<NextAction>,
|
||||
id: uint, // see comments in timer_cb
|
||||
}
|
||||
|
@ -33,15 +33,15 @@ pub enum NextAction {
|
|||
}
|
||||
|
||||
impl TimerWatcher {
|
||||
pub fn new(loop_: &mut Loop) -> ~TimerWatcher {
|
||||
pub fn new(io: &mut UvIoFactory) -> ~TimerWatcher {
|
||||
let handle = UvHandle::alloc(None::<TimerWatcher>, uvll::UV_TIMER);
|
||||
assert_eq!(unsafe {
|
||||
uvll::uv_timer_init(loop_.handle, handle)
|
||||
uvll::uv_timer_init(io.uv_loop(), handle)
|
||||
}, 0);
|
||||
let me = ~TimerWatcher {
|
||||
handle: handle,
|
||||
action: None,
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
id: 0,
|
||||
};
|
||||
return me.install();
|
||||
|
@ -59,7 +59,7 @@ impl TimerWatcher {
|
|||
}
|
||||
|
||||
impl HomingIO for TimerWatcher {
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
|
||||
fn home<'r>(&'r mut self) -> &'r mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl UvHandle<uvll::uv_timer_t> for TimerWatcher {
|
||||
|
@ -89,10 +89,11 @@ impl RtioTimer for TimerWatcher {
|
|||
// started, then we need to call stop on the timer.
|
||||
let _f = ForbidUnwind::new("timer");
|
||||
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|_sched, task| {
|
||||
let task: ~Task = Local::take();
|
||||
task.deschedule(1, |task| {
|
||||
self.action = Some(WakeTask(task));
|
||||
self.start(msecs, 0);
|
||||
Ok(())
|
||||
});
|
||||
self.stop();
|
||||
}
|
||||
|
@ -137,12 +138,11 @@ extern fn timer_cb(handle: *uvll::uv_timer_t, status: c_int) {
|
|||
|
||||
match timer.action.take_unwrap() {
|
||||
WakeTask(task) => {
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(task);
|
||||
task.wake().map(|t| t.reawaken(true));
|
||||
}
|
||||
SendOnce(chan) => { chan.try_send_deferred(()); }
|
||||
SendOnce(chan) => { chan.try_send(()); }
|
||||
SendMany(chan, id) => {
|
||||
chan.try_send_deferred(());
|
||||
chan.try_send(());
|
||||
|
||||
// Note that the above operation could have performed some form of
|
||||
// scheduling. This means that the timer may have decided to insert
|
||||
|
@ -169,7 +169,7 @@ impl Drop for TimerWatcher {
|
|||
let _action = {
|
||||
let _m = self.fire_homing_missile();
|
||||
self.stop();
|
||||
self.close_async_();
|
||||
self.close();
|
||||
self.action.take()
|
||||
};
|
||||
}
|
||||
|
@ -177,9 +177,9 @@ impl Drop for TimerWatcher {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::rt::rtio::RtioTimer;
|
||||
use super::super::local_loop;
|
||||
use super::TimerWatcher;
|
||||
|
||||
#[test]
|
||||
fn oneshot() {
|
||||
|
@ -207,9 +207,9 @@ mod test {
|
|||
let port = timer.period(1);
|
||||
port.recv();
|
||||
port.recv();
|
||||
let port = timer.period(1);
|
||||
port.recv();
|
||||
port.recv();
|
||||
let port2 = timer.period(1);
|
||||
port2.recv();
|
||||
port2.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -10,24 +10,23 @@
|
|||
|
||||
use std::libc;
|
||||
use std::io::IoError;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::RtioTTY;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
|
||||
use homing::{HomingIO, HomeHandle};
|
||||
use stream::StreamWatcher;
|
||||
use super::{Loop, UvError, UvHandle, uv_error_to_io_error};
|
||||
use uvio::HomingIO;
|
||||
use super::{UvError, UvHandle, uv_error_to_io_error};
|
||||
use uvio::UvIoFactory;
|
||||
use uvll;
|
||||
|
||||
pub struct TtyWatcher{
|
||||
tty: *uvll::uv_tty_t,
|
||||
stream: StreamWatcher,
|
||||
home: SchedHandle,
|
||||
home: HomeHandle,
|
||||
fd: libc::c_int,
|
||||
}
|
||||
|
||||
impl TtyWatcher {
|
||||
pub fn new(loop_: &Loop, fd: libc::c_int, readable: bool)
|
||||
pub fn new(io: &mut UvIoFactory, fd: libc::c_int, readable: bool)
|
||||
-> Result<TtyWatcher, UvError>
|
||||
{
|
||||
// libuv may succeed in giving us a handle (via uv_tty_init), but if the
|
||||
|
@ -56,14 +55,14 @@ impl TtyWatcher {
|
|||
// with attempting to open it as a tty.
|
||||
let handle = UvHandle::alloc(None::<TtyWatcher>, uvll::UV_TTY);
|
||||
match unsafe {
|
||||
uvll::uv_tty_init(loop_.handle, handle, fd as libc::c_int,
|
||||
uvll::uv_tty_init(io.uv_loop(), handle, fd as libc::c_int,
|
||||
readable as libc::c_int)
|
||||
} {
|
||||
0 => {
|
||||
Ok(TtyWatcher {
|
||||
tty: handle,
|
||||
stream: StreamWatcher::new(handle),
|
||||
home: get_handle_to_current_scheduler!(),
|
||||
home: io.make_handle(),
|
||||
fd: fd,
|
||||
})
|
||||
}
|
||||
|
@ -120,7 +119,7 @@ impl UvHandle<uvll::uv_tty_t> for TtyWatcher {
|
|||
}
|
||||
|
||||
impl HomingIO for TtyWatcher {
|
||||
fn home<'a>(&'a mut self) -> &'a mut SchedHandle { &mut self.home }
|
||||
fn home<'a>(&'a mut self) -> &'a mut HomeHandle { &mut self.home }
|
||||
}
|
||||
|
||||
impl Drop for TtyWatcher {
|
||||
|
|
|
@ -9,121 +9,41 @@
|
|||
// except according to those terms.
|
||||
|
||||
use std::c_str::CString;
|
||||
use std::cast;
|
||||
use std::comm::SharedChan;
|
||||
use std::libc::c_int;
|
||||
use std::libc;
|
||||
use std::path::Path;
|
||||
use std::io::IoError;
|
||||
use std::io::net::ip::SocketAddr;
|
||||
use std::io::process::ProcessConfig;
|
||||
use std::io;
|
||||
use std::rt::local::Local;
|
||||
use std::rt::rtio::*;
|
||||
use std::rt::sched::{Scheduler, SchedHandle};
|
||||
use std::rt::task::Task;
|
||||
use std::libc::{O_CREAT, O_APPEND, O_TRUNC, O_RDWR, O_RDONLY, O_WRONLY,
|
||||
S_IRUSR, S_IWUSR};
|
||||
use std::io::signal::Signum;
|
||||
use std::io::{FileMode, FileAccess, Open, Append, Truncate, Read, Write,
|
||||
ReadWrite, FileStat};
|
||||
use std::io::signal::Signum;
|
||||
use std::io;
|
||||
use std::libc::c_int;
|
||||
use std::libc::{O_CREAT, O_APPEND, O_TRUNC, O_RDWR, O_RDONLY, O_WRONLY, S_IRUSR,
|
||||
S_IWUSR};
|
||||
use std::libc;
|
||||
use std::path::Path;
|
||||
use std::rt::rtio;
|
||||
use std::rt::rtio::IoFactory;
|
||||
use ai = std::io::net::addrinfo;
|
||||
|
||||
#[cfg(test)] use std::unstable::run_in_bare_thread;
|
||||
|
||||
use super::*;
|
||||
use super::{uv_error_to_io_error, Loop};
|
||||
|
||||
use addrinfo::GetAddrInfoRequest;
|
||||
|
||||
pub trait HomingIO {
|
||||
|
||||
fn home<'r>(&'r mut self) -> &'r mut SchedHandle;
|
||||
|
||||
/// This function will move tasks to run on their home I/O scheduler. Note
|
||||
/// that this function does *not* pin the task to the I/O scheduler, but
|
||||
/// rather it simply moves it to running on the I/O scheduler.
|
||||
fn go_to_IO_home(&mut self) -> uint {
|
||||
use std::rt::sched::RunOnce;
|
||||
|
||||
let _f = ForbidUnwind::new("going home");
|
||||
|
||||
let current_sched_id = {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
sched.get().sched_id()
|
||||
};
|
||||
|
||||
// Only need to invoke a context switch if we're not on the right
|
||||
// scheduler.
|
||||
if current_sched_id != self.home().sched_id {
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
scheduler.deschedule_running_task_and_then(|_, task| {
|
||||
task.wake().map(|task| {
|
||||
self.home().send(RunOnce(task));
|
||||
});
|
||||
})
|
||||
}
|
||||
let current_sched_id = {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
sched.get().sched_id()
|
||||
};
|
||||
assert!(current_sched_id == self.home().sched_id);
|
||||
|
||||
self.home().sched_id
|
||||
}
|
||||
|
||||
/// Fires a single homing missile, returning another missile targeted back
|
||||
/// at the original home of this task. In other words, this function will
|
||||
/// move the local task to its I/O scheduler and then return an RAII wrapper
|
||||
/// which will return the task home.
|
||||
fn fire_homing_missile(&mut self) -> HomingMissile {
|
||||
HomingMissile { io_home: self.go_to_IO_home() }
|
||||
}
|
||||
|
||||
/// Same as `fire_homing_missile`, but returns the local I/O scheduler as
|
||||
/// well (the one that was homed to).
|
||||
fn fire_homing_missile_sched(&mut self) -> (HomingMissile, ~Scheduler) {
|
||||
// First, transplant ourselves to the home I/O scheduler
|
||||
let missile = self.fire_homing_missile();
|
||||
// Next (must happen next), grab the local I/O scheduler
|
||||
let io_sched: ~Scheduler = Local::take();
|
||||
|
||||
(missile, io_sched)
|
||||
}
|
||||
}
|
||||
|
||||
/// After a homing operation has been completed, this will return the current
|
||||
/// task back to its appropriate home (if applicable). The field is used to
|
||||
/// assert that we are where we think we are.
|
||||
struct HomingMissile {
|
||||
priv io_home: uint,
|
||||
}
|
||||
|
||||
impl HomingMissile {
|
||||
pub fn check(&self, msg: &'static str) {
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
let local_id = sched.get().sched_id();
|
||||
assert!(local_id == self.io_home, "{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HomingMissile {
|
||||
fn drop(&mut self) {
|
||||
let _f = ForbidUnwind::new("leaving home");
|
||||
|
||||
// It would truly be a sad day if we had moved off the home I/O
|
||||
// scheduler while we were doing I/O.
|
||||
self.check("task moved away from the home scheduler");
|
||||
|
||||
// If we were a homed task, then we must send ourselves back to the
|
||||
// original scheduler. Otherwise, we can just return and keep running
|
||||
if !Task::on_appropriate_sched() {
|
||||
let scheduler: ~Scheduler = Local::take();
|
||||
scheduler.deschedule_running_task_and_then(|_, task| {
|
||||
task.wake().map(|task| {
|
||||
Scheduler::run_task(task);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
use async::AsyncWatcher;
|
||||
use file::{FsRequest, FileWatcher};
|
||||
use queue::QueuePool;
|
||||
use homing::HomeHandle;
|
||||
use idle::IdleWatcher;
|
||||
use net::{TcpWatcher, TcpListener, UdpWatcher};
|
||||
use pipe::{PipeWatcher, PipeListener};
|
||||
use process::Process;
|
||||
use signal::SignalWatcher;
|
||||
use timer::TimerWatcher;
|
||||
use tty::TtyWatcher;
|
||||
use uvll;
|
||||
|
||||
// Obviously an Event Loop is always home.
|
||||
pub struct UvEventLoop {
|
||||
|
@ -132,49 +52,65 @@ pub struct UvEventLoop {
|
|||
|
||||
impl UvEventLoop {
|
||||
pub fn new() -> UvEventLoop {
|
||||
let mut loop_ = Loop::new();
|
||||
let handle_pool = QueuePool::new(&mut loop_);
|
||||
UvEventLoop {
|
||||
uvio: UvIoFactory(Loop::new())
|
||||
uvio: UvIoFactory {
|
||||
loop_: loop_,
|
||||
handle_pool: Some(handle_pool),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UvEventLoop {
|
||||
fn drop(&mut self) {
|
||||
self.uvio.uv_loop().close();
|
||||
// Must first destroy the pool of handles before we destroy the loop
|
||||
// because otherwise the contained async handle will be destroyed after
|
||||
// the loop is free'd (use-after-free). We also must free the uv handle
|
||||
// after the loop has been closed because during the closing of the loop
|
||||
// the handle is required to be used apparently.
|
||||
let handle = self.uvio.handle_pool.get_ref().handle();
|
||||
self.uvio.handle_pool.take();
|
||||
self.uvio.loop_.close();
|
||||
unsafe { uvll::free_handle(handle) }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop for UvEventLoop {
|
||||
impl rtio::EventLoop for UvEventLoop {
|
||||
fn run(&mut self) {
|
||||
self.uvio.uv_loop().run();
|
||||
self.uvio.loop_.run();
|
||||
}
|
||||
|
||||
fn callback(&mut self, f: proc()) {
|
||||
IdleWatcher::onetime(self.uvio.uv_loop(), f);
|
||||
IdleWatcher::onetime(&mut self.uvio.loop_, f);
|
||||
}
|
||||
|
||||
fn pausable_idle_callback(&mut self, cb: ~Callback) -> ~PausableIdleCallback {
|
||||
IdleWatcher::new(self.uvio.uv_loop(), cb) as ~PausableIdleCallback
|
||||
fn pausable_idle_callback(&mut self, cb: ~rtio::Callback)
|
||||
-> ~rtio::PausableIdleCallback
|
||||
{
|
||||
IdleWatcher::new(&mut self.uvio.loop_, cb) as ~rtio::PausableIdleCallback
|
||||
}
|
||||
|
||||
fn remote_callback(&mut self, f: ~Callback) -> ~RemoteCallback {
|
||||
~AsyncWatcher::new(self.uvio.uv_loop(), f) as ~RemoteCallback
|
||||
fn remote_callback(&mut self, f: ~rtio::Callback) -> ~rtio::RemoteCallback {
|
||||
~AsyncWatcher::new(&mut self.uvio.loop_, f) as ~rtio::RemoteCallback
|
||||
}
|
||||
|
||||
fn io<'a>(&'a mut self) -> Option<&'a mut IoFactory> {
|
||||
let factory = &mut self.uvio as &mut IoFactory;
|
||||
fn io<'a>(&'a mut self) -> Option<&'a mut rtio::IoFactory> {
|
||||
let factory = &mut self.uvio as &mut rtio::IoFactory;
|
||||
Some(factory)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[lang = "event_loop_factory"]
|
||||
pub extern "C" fn new_loop() -> ~EventLoop {
|
||||
~UvEventLoop::new() as ~EventLoop
|
||||
pub fn new_loop() -> ~rtio::EventLoop {
|
||||
~UvEventLoop::new() as ~rtio::EventLoop
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_callback_run_once() {
|
||||
use std::rt::rtio::EventLoop;
|
||||
do run_in_bare_thread {
|
||||
let mut event_loop = UvEventLoop::new();
|
||||
let mut count = 0;
|
||||
|
@ -187,11 +123,19 @@ fn test_callback_run_once() {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct UvIoFactory(Loop);
|
||||
pub struct UvIoFactory {
|
||||
loop_: Loop,
|
||||
priv handle_pool: Option<~QueuePool>,
|
||||
}
|
||||
|
||||
impl UvIoFactory {
|
||||
pub fn uv_loop<'a>(&'a mut self) -> &'a mut Loop {
|
||||
match self { &UvIoFactory(ref mut ptr) => ptr }
|
||||
pub fn uv_loop<'a>(&mut self) -> *uvll::uv_loop_t { self.loop_.handle }
|
||||
|
||||
pub fn make_handle(&mut self) -> HomeHandle {
|
||||
// It's understood by the homing code that the "local id" is just the
|
||||
// pointer of the local I/O factory cast to a uint.
|
||||
let id: uint = unsafe { cast::transmute_copy(&self) };
|
||||
HomeHandle::new(id, &mut **self.handle_pool.get_mut_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,46 +144,45 @@ impl IoFactory for UvIoFactory {
|
|||
// NB: This blocks the task waiting on the connection.
|
||||
// It would probably be better to return a future
|
||||
fn tcp_connect(&mut self, addr: SocketAddr)
|
||||
-> Result<~RtioTcpStream, IoError>
|
||||
-> Result<~rtio::RtioTcpStream, IoError>
|
||||
{
|
||||
match TcpWatcher::connect(self.uv_loop(), addr) {
|
||||
Ok(t) => Ok(~t as ~RtioTcpStream),
|
||||
match TcpWatcher::connect(self, addr) {
|
||||
Ok(t) => Ok(~t as ~rtio::RtioTcpStream),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn tcp_bind(&mut self, addr: SocketAddr) -> Result<~RtioTcpListener, IoError> {
|
||||
match TcpListener::bind(self.uv_loop(), addr) {
|
||||
Ok(t) => Ok(t as ~RtioTcpListener),
|
||||
fn tcp_bind(&mut self, addr: SocketAddr) -> Result<~rtio::RtioTcpListener, IoError> {
|
||||
match TcpListener::bind(self, addr) {
|
||||
Ok(t) => Ok(t as ~rtio::RtioTcpListener),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn udp_bind(&mut self, addr: SocketAddr) -> Result<~RtioUdpSocket, IoError> {
|
||||
match UdpWatcher::bind(self.uv_loop(), addr) {
|
||||
Ok(u) => Ok(~u as ~RtioUdpSocket),
|
||||
fn udp_bind(&mut self, addr: SocketAddr) -> Result<~rtio::RtioUdpSocket, IoError> {
|
||||
match UdpWatcher::bind(self, addr) {
|
||||
Ok(u) => Ok(~u as ~rtio::RtioUdpSocket),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn timer_init(&mut self) -> Result<~RtioTimer, IoError> {
|
||||
Ok(TimerWatcher::new(self.uv_loop()) as ~RtioTimer)
|
||||
fn timer_init(&mut self) -> Result<~rtio::RtioTimer, IoError> {
|
||||
Ok(TimerWatcher::new(self) as ~rtio::RtioTimer)
|
||||
}
|
||||
|
||||
fn get_host_addresses(&mut self, host: Option<&str>, servname: Option<&str>,
|
||||
hint: Option<ai::Hint>) -> Result<~[ai::Info], IoError> {
|
||||
let r = GetAddrInfoRequest::run(self.uv_loop(), host, servname, hint);
|
||||
let r = GetAddrInfoRequest::run(&self.loop_, host, servname, hint);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
|
||||
fn fs_from_raw_fd(&mut self, fd: c_int,
|
||||
close: CloseBehavior) -> ~RtioFileStream {
|
||||
let loop_ = Loop::wrap(self.uv_loop().handle);
|
||||
~FileWatcher::new(loop_, fd, close) as ~RtioFileStream
|
||||
close: rtio::CloseBehavior) -> ~rtio::RtioFileStream {
|
||||
~FileWatcher::new(self, fd, close) as ~rtio::RtioFileStream
|
||||
}
|
||||
|
||||
fn fs_open(&mut self, path: &CString, fm: FileMode, fa: FileAccess)
|
||||
-> Result<~RtioFileStream, IoError> {
|
||||
-> Result<~rtio::RtioFileStream, IoError> {
|
||||
let flags = match fm {
|
||||
io::Open => 0,
|
||||
io::Append => libc::O_APPEND,
|
||||
|
@ -254,117 +197,117 @@ impl IoFactory for UvIoFactory {
|
|||
libc::S_IRUSR | libc::S_IWUSR),
|
||||
};
|
||||
|
||||
match FsRequest::open(self.uv_loop(), path, flags as int, mode as int) {
|
||||
Ok(fs) => Ok(~fs as ~RtioFileStream),
|
||||
match FsRequest::open(self, path, flags as int, mode as int) {
|
||||
Ok(fs) => Ok(~fs as ~rtio::RtioFileStream),
|
||||
Err(e) => Err(uv_error_to_io_error(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn fs_unlink(&mut self, path: &CString) -> Result<(), IoError> {
|
||||
let r = FsRequest::unlink(self.uv_loop(), path);
|
||||
let r = FsRequest::unlink(&self.loop_, path);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_lstat(&mut self, path: &CString) -> Result<FileStat, IoError> {
|
||||
let r = FsRequest::lstat(self.uv_loop(), path);
|
||||
let r = FsRequest::lstat(&self.loop_, path);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_stat(&mut self, path: &CString) -> Result<FileStat, IoError> {
|
||||
let r = FsRequest::stat(self.uv_loop(), path);
|
||||
let r = FsRequest::stat(&self.loop_, path);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_mkdir(&mut self, path: &CString,
|
||||
perm: io::FilePermission) -> Result<(), IoError> {
|
||||
let r = FsRequest::mkdir(self.uv_loop(), path, perm as c_int);
|
||||
let r = FsRequest::mkdir(&self.loop_, path, perm as c_int);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_rmdir(&mut self, path: &CString) -> Result<(), IoError> {
|
||||
let r = FsRequest::rmdir(self.uv_loop(), path);
|
||||
let r = FsRequest::rmdir(&self.loop_, path);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_rename(&mut self, path: &CString, to: &CString) -> Result<(), IoError> {
|
||||
let r = FsRequest::rename(self.uv_loop(), path, to);
|
||||
let r = FsRequest::rename(&self.loop_, path, to);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_chmod(&mut self, path: &CString,
|
||||
perm: io::FilePermission) -> Result<(), IoError> {
|
||||
let r = FsRequest::chmod(self.uv_loop(), path, perm as c_int);
|
||||
let r = FsRequest::chmod(&self.loop_, path, perm as c_int);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_readdir(&mut self, path: &CString, flags: c_int)
|
||||
-> Result<~[Path], IoError>
|
||||
{
|
||||
let r = FsRequest::readdir(self.uv_loop(), path, flags);
|
||||
let r = FsRequest::readdir(&self.loop_, path, flags);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_link(&mut self, src: &CString, dst: &CString) -> Result<(), IoError> {
|
||||
let r = FsRequest::link(self.uv_loop(), src, dst);
|
||||
let r = FsRequest::link(&self.loop_, src, dst);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_symlink(&mut self, src: &CString, dst: &CString) -> Result<(), IoError> {
|
||||
let r = FsRequest::symlink(self.uv_loop(), src, dst);
|
||||
let r = FsRequest::symlink(&self.loop_, src, dst);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_chown(&mut self, path: &CString, uid: int, gid: int) -> Result<(), IoError> {
|
||||
let r = FsRequest::chown(self.uv_loop(), path, uid, gid);
|
||||
let r = FsRequest::chown(&self.loop_, path, uid, gid);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_readlink(&mut self, path: &CString) -> Result<Path, IoError> {
|
||||
let r = FsRequest::readlink(self.uv_loop(), path);
|
||||
let r = FsRequest::readlink(&self.loop_, path);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
fn fs_utime(&mut self, path: &CString, atime: u64, mtime: u64)
|
||||
-> Result<(), IoError>
|
||||
{
|
||||
let r = FsRequest::utime(self.uv_loop(), path, atime, mtime);
|
||||
let r = FsRequest::utime(&self.loop_, path, atime, mtime);
|
||||
r.map_err(uv_error_to_io_error)
|
||||
}
|
||||
|
||||
fn spawn(&mut self, config: ProcessConfig)
|
||||
-> Result<(~RtioProcess, ~[Option<~RtioPipe>]), IoError>
|
||||
-> Result<(~rtio::RtioProcess, ~[Option<~rtio::RtioPipe>]), IoError>
|
||||
{
|
||||
match Process::spawn(self.uv_loop(), config) {
|
||||
match Process::spawn(self, config) {
|
||||
Ok((p, io)) => {
|
||||
Ok((p as ~RtioProcess,
|
||||
io.move_iter().map(|i| i.map(|p| ~p as ~RtioPipe)).collect()))
|
||||
Ok((p as ~rtio::RtioProcess,
|
||||
io.move_iter().map(|i| i.map(|p| ~p as ~rtio::RtioPipe)).collect()))
|
||||
}
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_bind(&mut self, path: &CString) -> Result<~RtioUnixListener, IoError>
|
||||
fn unix_bind(&mut self, path: &CString) -> Result<~rtio::RtioUnixListener, IoError>
|
||||
{
|
||||
match PipeListener::bind(self.uv_loop(), path) {
|
||||
Ok(p) => Ok(p as ~RtioUnixListener),
|
||||
match PipeListener::bind(self, path) {
|
||||
Ok(p) => Ok(p as ~rtio::RtioUnixListener),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_connect(&mut self, path: &CString) -> Result<~RtioPipe, IoError> {
|
||||
match PipeWatcher::connect(self.uv_loop(), path) {
|
||||
Ok(p) => Ok(~p as ~RtioPipe),
|
||||
fn unix_connect(&mut self, path: &CString) -> Result<~rtio::RtioPipe, IoError> {
|
||||
match PipeWatcher::connect(self, path) {
|
||||
Ok(p) => Ok(~p as ~rtio::RtioPipe),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn tty_open(&mut self, fd: c_int, readable: bool)
|
||||
-> Result<~RtioTTY, IoError> {
|
||||
match TtyWatcher::new(self.uv_loop(), fd, readable) {
|
||||
Ok(tty) => Ok(~tty as ~RtioTTY),
|
||||
-> Result<~rtio::RtioTTY, IoError> {
|
||||
match TtyWatcher::new(self, fd, readable) {
|
||||
Ok(tty) => Ok(~tty as ~rtio::RtioTTY),
|
||||
Err(e) => Err(uv_error_to_io_error(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn pipe_open(&mut self, fd: c_int) -> Result<~RtioPipe, IoError> {
|
||||
match PipeWatcher::open(self.uv_loop(), fd) {
|
||||
Ok(s) => Ok(~s as ~RtioPipe),
|
||||
fn pipe_open(&mut self, fd: c_int) -> Result<~rtio::RtioPipe, IoError> {
|
||||
match PipeWatcher::open(self, fd) {
|
||||
Ok(s) => Ok(~s as ~rtio::RtioPipe),
|
||||
Err(e) => Err(uv_error_to_io_error(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn signal(&mut self, signum: Signum, channel: SharedChan<Signum>)
|
||||
-> Result<~RtioSignal, IoError> {
|
||||
match SignalWatcher::new(self.uv_loop(), signum, channel) {
|
||||
Ok(s) => Ok(s as ~RtioSignal),
|
||||
-> Result<~rtio::RtioSignal, IoError> {
|
||||
match SignalWatcher::new(self, signum, channel) {
|
||||
Ok(s) => Ok(s as ~rtio::RtioSignal),
|
||||
Err(e) => Err(uv_error_to_io_error(e)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ use std::libc;
|
|||
#[cfg(test)]
|
||||
use std::libc::uintptr_t;
|
||||
|
||||
pub use self::errors::*;
|
||||
pub use self::errors::{EACCES, ECONNREFUSED, ECONNRESET, EPIPE, ECONNABORTED,
|
||||
ECANCELED, EBADF, ENOTCONN, ENOENT};
|
||||
|
||||
pub static OK: c_int = 0;
|
||||
pub static EOF: c_int = -4095;
|
||||
|
@ -576,6 +577,8 @@ extern {
|
|||
|
||||
// generic uv functions
|
||||
pub fn uv_loop_delete(l: *uv_loop_t);
|
||||
pub fn uv_ref(t: *uv_handle_t);
|
||||
pub fn uv_unref(t: *uv_handle_t);
|
||||
pub fn uv_handle_size(ty: uv_handle_type) -> size_t;
|
||||
pub fn uv_req_size(ty: uv_req_type) -> size_t;
|
||||
pub fn uv_run(l: *uv_loop_t, mode: uv_run_mode) -> c_int;
|
||||
|
|
|
@ -20,10 +20,11 @@
|
|||
|
||||
use cast::transmute;
|
||||
use option::{Option, Some, None};
|
||||
use result::{Result, Ok, Err};
|
||||
use to_str::ToStr;
|
||||
use unstable::intrinsics::TypeId;
|
||||
use unstable::intrinsics;
|
||||
use util::Void;
|
||||
use unstable::intrinsics::TypeId;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Any trait
|
||||
|
@ -118,13 +119,13 @@ impl<'a> AnyMutRefExt<'a> for &'a mut Any {
|
|||
/// Extension methods for a owning `Any` trait object
|
||||
pub trait AnyOwnExt {
|
||||
/// Returns the boxed value if it is of type `T`, or
|
||||
/// `None` if it isn't.
|
||||
fn move<T: 'static>(self) -> Option<~T>;
|
||||
/// `Err(Self)` if it isn't.
|
||||
fn move<T: 'static>(self) -> Result<~T, Self>;
|
||||
}
|
||||
|
||||
impl AnyOwnExt for ~Any {
|
||||
#[inline]
|
||||
fn move<T: 'static>(self) -> Option<~T> {
|
||||
fn move<T: 'static>(self) -> Result<~T, ~Any> {
|
||||
if self.is::<T>() {
|
||||
unsafe {
|
||||
// Extract the pointer to the boxed value, temporary alias with self
|
||||
|
@ -133,10 +134,10 @@ impl AnyOwnExt for ~Any {
|
|||
// Prevent destructor on self being run
|
||||
intrinsics::forget(self);
|
||||
|
||||
Some(ptr)
|
||||
Ok(ptr)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,9 +156,8 @@ impl<'a> ToStr for &'a Any {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use prelude::*;
|
||||
use super::*;
|
||||
use super::AnyRefExt;
|
||||
use option::{Some, None};
|
||||
|
||||
#[deriving(Eq)]
|
||||
struct Test;
|
||||
|
@ -384,13 +384,19 @@ mod tests {
|
|||
let a = ~8u as ~Any;
|
||||
let b = ~Test as ~Any;
|
||||
|
||||
assert_eq!(a.move(), Some(~8u));
|
||||
assert_eq!(b.move(), Some(~Test));
|
||||
match a.move::<uint>() {
|
||||
Ok(a) => { assert_eq!(a, ~8u); }
|
||||
Err(..) => fail!()
|
||||
}
|
||||
match b.move::<Test>() {
|
||||
Ok(a) => { assert_eq!(a, ~Test); }
|
||||
Err(..) => fail!()
|
||||
}
|
||||
|
||||
let a = ~8u as ~Any;
|
||||
let b = ~Test as ~Any;
|
||||
|
||||
assert_eq!(a.move(), None::<~Test>);
|
||||
assert_eq!(b.move(), None::<~uint>);
|
||||
assert!(a.move::<~Test>().is_err());
|
||||
assert!(b.move::<~uint>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,337 +0,0 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! One of the major goals behind this channel implementation is to work
|
||||
//! seamlessly on and off the runtime. This also means that the code isn't
|
||||
//! littered with "if is_green() { ... } else { ... }". Right now, the rest of
|
||||
//! the runtime isn't quite ready to for this abstraction to be done very
|
||||
//! nicely, so the conditional "if green" blocks are all contained in this inner
|
||||
//! module.
|
||||
//!
|
||||
//! The goal of this module is to mirror what the runtime "should be", not the
|
||||
//! state that it is currently in today. You'll notice that there is no mention
|
||||
//! of schedulers or is_green inside any of the channel code, it is currently
|
||||
//! entirely contained in this one module.
|
||||
//!
|
||||
//! In the ideal world, nothing in this module exists and it is all implemented
|
||||
//! elsewhere in the runtime (in the proper location). All of this code is
|
||||
//! structured in order to easily refactor this to the correct location whenever
|
||||
//! we have the trait objects in place to serve as the boundary of the
|
||||
//! abstraction.
|
||||
|
||||
use iter::{range, Iterator};
|
||||
use ops::Drop;
|
||||
use option::{Some, None, Option};
|
||||
use rt::local::Local;
|
||||
use rt::sched::{SchedHandle, Scheduler, TaskFromFriend};
|
||||
use rt::thread::Thread;
|
||||
use rt;
|
||||
use unstable::mutex::Mutex;
|
||||
use unstable::sync::UnsafeArc;
|
||||
|
||||
// A task handle is a method of waking up a blocked task. The handle itself
|
||||
// is completely opaque and only has a wake() method defined on it. This
|
||||
// method will wake the method regardless of the context of the thread which
|
||||
// is currently calling wake().
|
||||
//
|
||||
// This abstraction should be able to be created when putting a task to
|
||||
// sleep. This should basically be a method on whatever the local Task is,
|
||||
// consuming the local Task.
|
||||
|
||||
pub struct TaskHandle {
|
||||
priv inner: TaskRepr
|
||||
}
|
||||
enum TaskRepr {
|
||||
Green(rt::BlockedTask, *mut SchedHandle),
|
||||
Native(NativeWakeupStyle),
|
||||
}
|
||||
enum NativeWakeupStyle {
|
||||
ArcWakeup(UnsafeArc<Mutex>), // shared mutex to synchronize on
|
||||
LocalWakeup(*mut Mutex), // synchronize on the task-local mutex
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
// Signal that this handle should be woken up. The `can_resched`
|
||||
// argument indicates whether the current task could possibly be
|
||||
// rescheduled or not. This does not have a lot of meaning for the
|
||||
// native case, but for an M:N case it indicates whether a context
|
||||
// switch can happen or not.
|
||||
pub fn wake(self, can_resched: bool) {
|
||||
match self.inner {
|
||||
Green(task, handle) => {
|
||||
// If we have a local scheduler, then use that to run the
|
||||
// blocked task, otherwise we can use the handle to send the
|
||||
// task back to its home.
|
||||
if rt::in_green_task_context() {
|
||||
if can_resched {
|
||||
task.wake().map(Scheduler::run_task);
|
||||
} else {
|
||||
let mut s: ~Scheduler = Local::take();
|
||||
s.enqueue_blocked_task(task);
|
||||
Local::put(s);
|
||||
}
|
||||
} else {
|
||||
let task = match task.wake() {
|
||||
Some(task) => task, None => return
|
||||
};
|
||||
// XXX: this is not an easy section of code to refactor.
|
||||
// If this handle is owned by the Task (which it
|
||||
// should be), then this would be a use-after-free
|
||||
// because once the task is pushed onto the message
|
||||
// queue, the handle is gone.
|
||||
//
|
||||
// Currently the handle is instead owned by the
|
||||
// Port/Chan pair, which means that because a
|
||||
// channel is invoking this method the handle will
|
||||
// continue to stay alive for the entire duration
|
||||
// of this method. This will require thought when
|
||||
// moving the handle into the task.
|
||||
unsafe { (*handle).send(TaskFromFriend(task)) }
|
||||
}
|
||||
}
|
||||
|
||||
// Note that there are no use-after-free races in this code. In
|
||||
// the arc-case, we own the lock, and in the local case, we're
|
||||
// using a lock so it's guranteed that they aren't running while
|
||||
// we hold the lock.
|
||||
Native(ArcWakeup(lock)) => {
|
||||
unsafe {
|
||||
let lock = lock.get();
|
||||
(*lock).lock();
|
||||
(*lock).signal();
|
||||
(*lock).unlock();
|
||||
}
|
||||
}
|
||||
Native(LocalWakeup(lock)) => {
|
||||
unsafe {
|
||||
(*lock).lock();
|
||||
(*lock).signal();
|
||||
(*lock).unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trashes handle to this task. This ensures that necessary memory is
|
||||
// deallocated, and there may be some extra assertions as well.
|
||||
pub fn trash(self) {
|
||||
match self.inner {
|
||||
Green(task, _) => task.assert_already_awake(),
|
||||
Native(..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This structure is an abstraction of what should be stored in the local
|
||||
// task itself. This data is currently stored inside of each channel, but
|
||||
// this should rather be stored in each task (and channels will still
|
||||
// continue to lazily initialize this data).
|
||||
|
||||
pub struct TaskData {
|
||||
priv handle: Option<SchedHandle>,
|
||||
priv lock: Mutex,
|
||||
}
|
||||
|
||||
impl TaskData {
|
||||
pub fn new() -> TaskData {
|
||||
TaskData {
|
||||
handle: None,
|
||||
lock: unsafe { Mutex::empty() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TaskData {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.lock.destroy() }
|
||||
}
|
||||
}
|
||||
|
||||
// Now this is the really fun part. This is where all the M:N/1:1-agnostic
|
||||
// along with recv/select-agnostic blocking information goes. A "blocking
|
||||
// context" is really just a stack-allocated structure (which is probably
|
||||
// fine to be a stack-trait-object).
|
||||
//
|
||||
// This has some particularly strange interfaces, but the reason for all
|
||||
// this is to support selection/recv/1:1/M:N all in one bundle.
|
||||
|
||||
pub struct BlockingContext<'a> {
|
||||
priv inner: BlockingRepr<'a>
|
||||
}
|
||||
|
||||
enum BlockingRepr<'a> {
|
||||
GreenBlock(rt::BlockedTask, &'a mut Scheduler),
|
||||
NativeBlock(Option<UnsafeArc<Mutex>>),
|
||||
}
|
||||
|
||||
impl<'a> BlockingContext<'a> {
|
||||
// Creates one blocking context. The data provided should in theory be
|
||||
// acquired from the local task, but it is instead acquired from the
|
||||
// channel currently.
|
||||
//
|
||||
// This function will call `f` with a blocking context, plus the data
|
||||
// that it is given. This function will then return whether this task
|
||||
// should actually go to sleep or not. If `true` is returned, then this
|
||||
// function does not return until someone calls `wake()` on the task.
|
||||
// If `false` is returned, then this function immediately returns.
|
||||
//
|
||||
// # Safety note
|
||||
//
|
||||
// Note that this stack closure may not be run on the same stack as when
|
||||
// this function was called. This means that the environment of this
|
||||
// stack closure could be unsafely aliased. This is currently prevented
|
||||
// through the guarantee that this function will never return before `f`
|
||||
// finishes executing.
|
||||
pub fn one(data: &mut TaskData,
|
||||
f: |BlockingContext, &mut TaskData| -> bool) {
|
||||
if rt::in_green_task_context() {
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|sched, task| {
|
||||
let ctx = BlockingContext { inner: GreenBlock(task, sched) };
|
||||
// no need to do something on success/failure other than
|
||||
// returning because the `block` function for a BlockingContext
|
||||
// takes care of reawakening itself if the blocking procedure
|
||||
// fails. If this function is successful, then we're already
|
||||
// blocked, and if it fails, the task will already be
|
||||
// rescheduled.
|
||||
f(ctx, data);
|
||||
});
|
||||
} else {
|
||||
unsafe { data.lock.lock(); }
|
||||
let ctx = BlockingContext { inner: NativeBlock(None) };
|
||||
if f(ctx, data) {
|
||||
unsafe { data.lock.wait(); }
|
||||
}
|
||||
unsafe { data.lock.unlock(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Creates many blocking contexts. The intended use case for this
|
||||
// function is selection over a number of ports. This will create `amt`
|
||||
// blocking contexts, yielding them to `f` in turn. If `f` returns
|
||||
// false, then this function aborts and returns immediately. If `f`
|
||||
// repeatedly returns `true` `amt` times, then this function will block.
|
||||
pub fn many(amt: uint, f: |BlockingContext| -> bool) {
|
||||
if rt::in_green_task_context() {
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|sched, task| {
|
||||
for handle in task.make_selectable(amt) {
|
||||
let ctx = BlockingContext {
|
||||
inner: GreenBlock(handle, sched)
|
||||
};
|
||||
// see comment above in `one` for why no further action is
|
||||
// necessary here
|
||||
if !f(ctx) { break }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// In the native case, our decision to block must be shared
|
||||
// amongst all of the channels. It may be possible to
|
||||
// stack-allocate this mutex (instead of putting it in an
|
||||
// UnsafeArc box), but for now in order to prevent
|
||||
// use-after-free trivially we place this into a box and then
|
||||
// pass that around.
|
||||
unsafe {
|
||||
let mtx = UnsafeArc::new(Mutex::new());
|
||||
(*mtx.get()).lock();
|
||||
let success = range(0, amt).all(|_| {
|
||||
f(BlockingContext {
|
||||
inner: NativeBlock(Some(mtx.clone()))
|
||||
})
|
||||
});
|
||||
if success {
|
||||
(*mtx.get()).wait();
|
||||
}
|
||||
(*mtx.get()).unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function will consume this BlockingContext, and optionally block
|
||||
// if according to the atomic `decision` function. The semantics of this
|
||||
// functions are:
|
||||
//
|
||||
// * `slot` is required to be a `None`-slot (which is owned by the
|
||||
// channel)
|
||||
// * The `slot` will be filled in with a blocked version of the current
|
||||
// task (with `wake`-ability if this function is successful).
|
||||
// * If the `decision` function returns true, then this function
|
||||
// immediately returns having relinquished ownership of the task.
|
||||
// * If the `decision` function returns false, then the `slot` is reset
|
||||
// to `None` and the task is re-scheduled if necessary (remember that
|
||||
// the task will not resume executing before the outer `one` or
|
||||
// `many` function has returned. This function is expected to have a
|
||||
// release memory fence in order for the modifications of `to_wake` to be
|
||||
// visible to other tasks. Code which attempts to read `to_wake` should
|
||||
// have an acquiring memory fence to guarantee that this write is
|
||||
// visible.
|
||||
//
|
||||
// This function will return whether the blocking occurred or not.
|
||||
pub fn block(self,
|
||||
data: &mut TaskData,
|
||||
slot: &mut Option<TaskHandle>,
|
||||
decision: || -> bool) -> bool {
|
||||
assert!(slot.is_none());
|
||||
match self.inner {
|
||||
GreenBlock(task, sched) => {
|
||||
if data.handle.is_none() {
|
||||
data.handle = Some(sched.make_handle());
|
||||
}
|
||||
let handle = data.handle.get_mut_ref() as *mut SchedHandle;
|
||||
*slot = Some(TaskHandle { inner: Green(task, handle) });
|
||||
|
||||
if !decision() {
|
||||
match slot.take_unwrap().inner {
|
||||
Green(task, _) => sched.enqueue_blocked_task(task),
|
||||
Native(..) => unreachable!()
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
NativeBlock(shared) => {
|
||||
*slot = Some(TaskHandle {
|
||||
inner: Native(match shared {
|
||||
Some(arc) => ArcWakeup(arc),
|
||||
None => LocalWakeup(&mut data.lock as *mut Mutex),
|
||||
})
|
||||
});
|
||||
|
||||
if !decision() {
|
||||
*slot = None;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Agnostic method of forcing a yield of the current task
|
||||
pub fn yield_now() {
|
||||
if rt::in_green_task_context() {
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.yield_now();
|
||||
} else {
|
||||
Thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
// Agnostic method of "maybe yielding" in order to provide fairness
|
||||
pub fn maybe_yield() {
|
||||
if rt::in_green_task_context() {
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.maybe_yield();
|
||||
} else {
|
||||
// the OS decides fairness, nothing for us to do.
|
||||
}
|
||||
}
|
|
@ -233,14 +233,16 @@ use iter::Iterator;
|
|||
use kinds::Send;
|
||||
use ops::Drop;
|
||||
use option::{Option, Some, None};
|
||||
use result::{Ok, Err};
|
||||
use rt::local::Local;
|
||||
use rt::task::{Task, BlockedTask};
|
||||
use rt::thread::Thread;
|
||||
use unstable::atomics::{AtomicInt, AtomicBool, SeqCst, Relaxed};
|
||||
use sync::atomics::{AtomicInt, AtomicBool, SeqCst, Relaxed};
|
||||
use vec::{ImmutableVector, OwnedVector};
|
||||
|
||||
use spsc = rt::spsc_queue;
|
||||
use mpsc = rt::mpsc_queue;
|
||||
use spsc = sync::spsc_queue;
|
||||
use mpsc = sync::mpsc_queue;
|
||||
|
||||
use self::imp::{TaskHandle, TaskData, BlockingContext};
|
||||
pub use self::select::Select;
|
||||
|
||||
macro_rules! test (
|
||||
|
@ -248,24 +250,26 @@ macro_rules! test (
|
|||
mod $name {
|
||||
#[allow(unused_imports)];
|
||||
|
||||
use util;
|
||||
use super::super::*;
|
||||
use native;
|
||||
use prelude::*;
|
||||
use super::*;
|
||||
use super::super::*;
|
||||
use task;
|
||||
use util;
|
||||
|
||||
fn f() $b
|
||||
|
||||
$($a)* #[test] fn uv() { f() }
|
||||
$($a)* #[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#11003)
|
||||
fn native() {
|
||||
use unstable::run_in_bare_thread;
|
||||
run_in_bare_thread(f);
|
||||
$($a)* #[test] fn native() {
|
||||
use native;
|
||||
let (p, c) = Chan::new();
|
||||
do native::task::spawn { c.send(f()) }
|
||||
p.recv();
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
mod imp;
|
||||
mod select;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -326,9 +330,7 @@ pub struct SharedChan<T> {
|
|||
struct Packet {
|
||||
cnt: AtomicInt, // How many items are on this channel
|
||||
steals: int, // How many times has a port received without blocking?
|
||||
to_wake: Option<TaskHandle>, // Task to wake up
|
||||
|
||||
data: TaskData,
|
||||
to_wake: Option<BlockedTask>, // Task to wake up
|
||||
|
||||
// This lock is used to wake up native threads blocked in select. The
|
||||
// `lock` field is not used because the thread blocking in select must
|
||||
|
@ -343,6 +345,7 @@ struct Packet {
|
|||
selection_id: uint,
|
||||
select_next: *mut Packet,
|
||||
select_prev: *mut Packet,
|
||||
recv_cnt: int,
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -358,13 +361,13 @@ impl Packet {
|
|||
cnt: AtomicInt::new(0),
|
||||
steals: 0,
|
||||
to_wake: None,
|
||||
data: TaskData::new(),
|
||||
channels: AtomicInt::new(1),
|
||||
|
||||
selecting: AtomicBool::new(false),
|
||||
selection_id: 0,
|
||||
select_next: 0 as *mut Packet,
|
||||
select_prev: 0 as *mut Packet,
|
||||
recv_cnt: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,7 +421,10 @@ impl Packet {
|
|||
// This function must have had at least an acquire fence before it to be
|
||||
// properly called.
|
||||
fn wakeup(&mut self, can_resched: bool) {
|
||||
self.to_wake.take_unwrap().wake(can_resched);
|
||||
match self.to_wake.take_unwrap().wake() {
|
||||
Some(task) => task.reawaken(can_resched),
|
||||
None => {}
|
||||
}
|
||||
self.selecting.store(false, Relaxed);
|
||||
}
|
||||
|
||||
|
@ -490,7 +496,7 @@ impl Packet {
|
|||
match self.channels.fetch_sub(1, SeqCst) {
|
||||
1 => {
|
||||
match self.cnt.swap(DISCONNECTED, SeqCst) {
|
||||
-1 => { self.wakeup(false); }
|
||||
-1 => { self.wakeup(true); }
|
||||
DISCONNECTED => {}
|
||||
n => { assert!(n >= 0); }
|
||||
}
|
||||
|
@ -531,9 +537,6 @@ impl<T: Send> Chan<T> {
|
|||
/// port.
|
||||
///
|
||||
/// Rust channels are infinitely buffered so this method will never block.
|
||||
/// This method may trigger a rescheduling, however, in order to wake up a
|
||||
/// blocked receiver (if one is present). If no scheduling is desired, then
|
||||
/// the `send_deferred` guarantees that there will be no reschedulings.
|
||||
///
|
||||
/// # Failure
|
||||
///
|
||||
|
@ -555,15 +558,6 @@ impl<T: Send> Chan<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This function is equivalent in the semantics of `send`, but it
|
||||
/// guarantees that a rescheduling will never occur when this method is
|
||||
/// called.
|
||||
pub fn send_deferred(&self, t: T) {
|
||||
if !self.try_send_deferred(t) {
|
||||
fail!("sending on a closed channel");
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to send a value on this channel, returning whether it was
|
||||
/// successfully sent.
|
||||
///
|
||||
|
@ -579,9 +573,8 @@ impl<T: Send> Chan<T> {
|
|||
/// be tolerated, then this method should be used instead.
|
||||
pub fn try_send(&self, t: T) -> bool { self.try(t, true) }
|
||||
|
||||
/// This function is equivalent in the semantics of `try_send`, but it
|
||||
/// guarantees that a rescheduling will never occur when this method is
|
||||
/// called.
|
||||
/// This function will not stick around for very long. The purpose of this
|
||||
/// function is to guarantee that no rescheduling is performed.
|
||||
pub fn try_send_deferred(&self, t: T) -> bool { self.try(t, false) }
|
||||
|
||||
fn try(&self, t: T, can_resched: bool) -> bool {
|
||||
|
@ -606,8 +599,9 @@ impl<T: Send> Chan<T> {
|
|||
// the TLS overhead can be a bit much.
|
||||
n => {
|
||||
assert!(n >= 0);
|
||||
if can_resched && n > 0 && n % RESCHED_FREQ == 0 {
|
||||
imp::maybe_yield();
|
||||
if n > 0 && n % RESCHED_FREQ == 0 {
|
||||
let task: ~Task = Local::take();
|
||||
task.maybe_yield();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -642,25 +636,9 @@ impl<T: Send> SharedChan<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This function is equivalent in the semantics of `send`, but it
|
||||
/// guarantees that a rescheduling will never occur when this method is
|
||||
/// called.
|
||||
pub fn send_deferred(&self, t: T) {
|
||||
if !self.try_send_deferred(t) {
|
||||
fail!("sending on a closed channel");
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent method to `try_send` on the `Chan` type (using the same
|
||||
/// semantics)
|
||||
pub fn try_send(&self, t: T) -> bool { self.try(t, true) }
|
||||
|
||||
/// This function is equivalent in the semantics of `try_send`, but it
|
||||
/// guarantees that a rescheduling will never occur when this method is
|
||||
/// called.
|
||||
pub fn try_send_deferred(&self, t: T) -> bool { self.try(t, false) }
|
||||
|
||||
fn try(&self, t: T, can_resched: bool) -> bool {
|
||||
pub fn try_send(&self, t: T) -> bool {
|
||||
unsafe {
|
||||
// Note that the multiple sender case is a little tricker
|
||||
// semantically than the single sender case. The logic for
|
||||
|
@ -697,10 +675,11 @@ impl<T: Send> SharedChan<T> {
|
|||
|
||||
match (*packet).increment() {
|
||||
DISCONNECTED => {} // oh well, we tried
|
||||
-1 => { (*packet).wakeup(can_resched); }
|
||||
-1 => { (*packet).wakeup(true); }
|
||||
n => {
|
||||
if can_resched && n > 0 && n % RESCHED_FREQ == 0 {
|
||||
imp::maybe_yield();
|
||||
if n > 0 && n % RESCHED_FREQ == 0 {
|
||||
let task: ~Task = Local::take();
|
||||
task.maybe_yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -768,6 +747,18 @@ impl<T: Send> Port<T> {
|
|||
// This is a "best effort" situation, so if a queue is inconsistent just
|
||||
// don't worry about it.
|
||||
let this = unsafe { cast::transmute_mut(self) };
|
||||
|
||||
// See the comment about yielding on sends, but the same applies here.
|
||||
// If a thread is spinning in try_recv we should try
|
||||
unsafe {
|
||||
let packet = this.queue.packet();
|
||||
(*packet).recv_cnt += 1;
|
||||
if (*packet).recv_cnt % RESCHED_FREQ == 0 {
|
||||
let task: ~Task = Local::take();
|
||||
task.maybe_yield();
|
||||
}
|
||||
}
|
||||
|
||||
let ret = match this.queue {
|
||||
SPSC(ref mut queue) => queue.pop(),
|
||||
MPSC(ref mut queue) => match queue.pop() {
|
||||
|
@ -840,15 +831,22 @@ impl<T: Send> Port<T> {
|
|||
unsafe {
|
||||
this = cast::transmute_mut(self);
|
||||
packet = this.queue.packet();
|
||||
BlockingContext::one(&mut (*packet).data, |ctx, data| {
|
||||
ctx.block(data, &mut (*packet).to_wake, || (*packet).decrement())
|
||||
let task: ~Task = Local::take();
|
||||
task.deschedule(1, |task| {
|
||||
assert!((*packet).to_wake.is_none());
|
||||
(*packet).to_wake = Some(task);
|
||||
if (*packet).decrement() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((*packet).to_wake.take_unwrap())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let data = self.try_recv_inc(false);
|
||||
if data.is_none() &&
|
||||
unsafe { (*packet).cnt.load(SeqCst) } != DISCONNECTED {
|
||||
fail!("bug: woke up too soon");
|
||||
fail!("bug: woke up too soon {}", unsafe { (*packet).cnt.load(SeqCst) });
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
@ -880,10 +878,16 @@ impl<T: Send> Drop for Port<T> {
|
|||
mod test {
|
||||
use prelude::*;
|
||||
|
||||
use task;
|
||||
use rt::thread::Thread;
|
||||
use native;
|
||||
use os;
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
|
||||
pub fn stress_factor() -> uint {
|
||||
match os::getenv("RUST_TEST_STRESS") {
|
||||
Some(val) => from_str::<uint>(val).unwrap(),
|
||||
None => 1,
|
||||
}
|
||||
}
|
||||
|
||||
test!(fn smoke() {
|
||||
let (p, c) = Chan::new();
|
||||
|
@ -910,99 +914,88 @@ mod test {
|
|||
assert_eq!(p.recv(), 1);
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn smoke_threads() {
|
||||
test!(fn smoke_threads() {
|
||||
let (p, c) = Chan::new();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
c.send(1);
|
||||
}
|
||||
assert_eq!(p.recv(), 1);
|
||||
}
|
||||
})
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_port_gone() {
|
||||
test!(fn smoke_port_gone() {
|
||||
let (p, c) = Chan::new();
|
||||
drop(p);
|
||||
c.send(1);
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_shared_port_gone() {
|
||||
test!(fn smoke_shared_port_gone() {
|
||||
let (p, c) = SharedChan::new();
|
||||
drop(p);
|
||||
c.send(1);
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_shared_port_gone2() {
|
||||
test!(fn smoke_shared_port_gone2() {
|
||||
let (p, c) = SharedChan::new();
|
||||
drop(p);
|
||||
let c2 = c.clone();
|
||||
drop(c);
|
||||
c2.send(1);
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn port_gone_concurrent() {
|
||||
test!(fn port_gone_concurrent() {
|
||||
let (p, c) = Chan::new();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
p.recv();
|
||||
}
|
||||
loop { c.send(1) }
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn port_gone_concurrent_shared() {
|
||||
test!(fn port_gone_concurrent_shared() {
|
||||
let (p, c) = SharedChan::new();
|
||||
let c1 = c.clone();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
p.recv();
|
||||
}
|
||||
loop {
|
||||
c.send(1);
|
||||
c1.send(1);
|
||||
}
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_chan_gone() {
|
||||
test!(fn smoke_chan_gone() {
|
||||
let (p, c) = Chan::<int>::new();
|
||||
drop(c);
|
||||
p.recv();
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn smoke_chan_gone_shared() {
|
||||
test!(fn smoke_chan_gone_shared() {
|
||||
let (p, c) = SharedChan::<()>::new();
|
||||
let c2 = c.clone();
|
||||
drop(c);
|
||||
drop(c2);
|
||||
p.recv();
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn chan_gone_concurrent() {
|
||||
test!(fn chan_gone_concurrent() {
|
||||
let (p, c) = Chan::new();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
c.send(1);
|
||||
c.send(1);
|
||||
}
|
||||
loop { p.recv(); }
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test]
|
||||
fn stress() {
|
||||
test!(fn stress() {
|
||||
let (p, c) = Chan::new();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
for _ in range(0, 10000) { c.send(1); }
|
||||
}
|
||||
for _ in range(0, 10000) {
|
||||
assert_eq!(p.recv(), 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn stress_shared() {
|
||||
test!(fn stress_shared() {
|
||||
static AMT: uint = 10000;
|
||||
static NTHREADS: uint = 8;
|
||||
let (p, c) = SharedChan::<int>::new();
|
||||
|
@ -1018,47 +1011,53 @@ mod test {
|
|||
|
||||
for _ in range(0, NTHREADS) {
|
||||
let c = c.clone();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do spawn {
|
||||
for _ in range(0, AMT) { c.send(1); }
|
||||
}
|
||||
}
|
||||
p1.recv();
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#11003)
|
||||
fn send_from_outside_runtime() {
|
||||
let (p, c) = Chan::<int>::new();
|
||||
let (p1, c1) = Chan::new();
|
||||
let (port, chan) = SharedChan::new();
|
||||
let chan2 = chan.clone();
|
||||
do spawn {
|
||||
c1.send(());
|
||||
for _ in range(0, 40) {
|
||||
assert_eq!(p.recv(), 1);
|
||||
}
|
||||
chan2.send(());
|
||||
}
|
||||
p1.recv();
|
||||
let t = do Thread::start {
|
||||
do native::task::spawn {
|
||||
for _ in range(0, 40) {
|
||||
c.send(1);
|
||||
}
|
||||
};
|
||||
t.join();
|
||||
chan.send(());
|
||||
}
|
||||
port.recv();
|
||||
port.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#11003)
|
||||
fn recv_from_outside_runtime() {
|
||||
let (p, c) = Chan::<int>::new();
|
||||
let t = do Thread::start {
|
||||
let (dp, dc) = Chan::new();
|
||||
do native::task::spawn {
|
||||
for _ in range(0, 40) {
|
||||
assert_eq!(p.recv(), 1);
|
||||
}
|
||||
dc.send(());
|
||||
};
|
||||
for _ in range(0, 40) {
|
||||
c.send(1);
|
||||
}
|
||||
t.join();
|
||||
dp.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1066,173 +1065,132 @@ mod test {
|
|||
fn no_runtime() {
|
||||
let (p1, c1) = Chan::<int>::new();
|
||||
let (p2, c2) = Chan::<int>::new();
|
||||
let t1 = do Thread::start {
|
||||
let (port, chan) = SharedChan::new();
|
||||
let chan2 = chan.clone();
|
||||
do native::task::spawn {
|
||||
assert_eq!(p1.recv(), 1);
|
||||
c2.send(2);
|
||||
};
|
||||
let t2 = do Thread::start {
|
||||
chan2.send(());
|
||||
}
|
||||
do native::task::spawn {
|
||||
c1.send(1);
|
||||
assert_eq!(p2.recv(), 2);
|
||||
};
|
||||
t1.join();
|
||||
t2.join();
|
||||
chan.send(());
|
||||
}
|
||||
port.recv();
|
||||
port.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_close_port_first() {
|
||||
test!(fn oneshot_single_thread_close_port_first() {
|
||||
// Simple test of closing without sending
|
||||
do run_in_newsched_task {
|
||||
let (port, _chan) = Chan::<int>::new();
|
||||
{ let _p = port; }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_close_chan_first() {
|
||||
test!(fn oneshot_single_thread_close_chan_first() {
|
||||
// Simple test of closing without sending
|
||||
do run_in_newsched_task {
|
||||
let (_port, chan) = Chan::<int>::new();
|
||||
{ let _c = chan; }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test] #[should_fail]
|
||||
fn oneshot_single_thread_send_port_close() {
|
||||
test!(fn oneshot_single_thread_send_port_close() {
|
||||
// Testing that the sender cleans up the payload if receiver is closed
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
{ let _p = port; }
|
||||
chan.send(~0);
|
||||
}
|
||||
} #[should_fail])
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_recv_chan_close() {
|
||||
test!(fn oneshot_single_thread_recv_chan_close() {
|
||||
// Receiving on a closed chan will fail
|
||||
do run_in_newsched_task {
|
||||
let res = do spawntask_try {
|
||||
let res = do task::try {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
{ let _c = chan; }
|
||||
port.recv();
|
||||
};
|
||||
// What is our res?
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_send_then_recv() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_send_then_recv() {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
chan.send(~10);
|
||||
assert!(port.recv() == ~10);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_try_send_open() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_try_send_open() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
assert!(chan.try_send(10));
|
||||
assert!(port.recv() == 10);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_try_send_closed() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_try_send_closed() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
{ let _p = port; }
|
||||
assert!(!chan.try_send(10));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_try_recv_open() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_try_recv_open() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
chan.send(10);
|
||||
assert!(port.try_recv() == Some(10));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_try_recv_closed() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_try_recv_closed() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
{ let _c = chan; }
|
||||
assert!(port.recv_opt() == None);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_peek_data() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_peek_data() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
assert!(port.try_recv().is_none());
|
||||
chan.send(10);
|
||||
assert!(port.try_recv().is_some());
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_peek_close() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_peek_close() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
{ let _c = chan; }
|
||||
assert!(port.try_recv().is_none());
|
||||
assert!(port.try_recv().is_none());
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_single_thread_peek_open() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_single_thread_peek_open() {
|
||||
let (port, _) = Chan::<int>::new();
|
||||
assert!(port.try_recv().is_none());
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_task_recv_then_send() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_multi_task_recv_then_send() {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
do spawntask {
|
||||
do spawn {
|
||||
assert!(port.recv() == ~10);
|
||||
}
|
||||
|
||||
chan.send(~10);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_task_recv_then_close() {
|
||||
do run_in_newsched_task {
|
||||
test!(fn oneshot_multi_task_recv_then_close() {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
do spawntask_later {
|
||||
do spawn {
|
||||
let _chan = chan;
|
||||
}
|
||||
let res = do spawntask_try {
|
||||
let res = do task::try {
|
||||
assert!(port.recv() == ~10);
|
||||
};
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_thread_close_stress() {
|
||||
stress_factor().times(|| {
|
||||
do run_in_newsched_task {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
let thread = do spawntask_thread {
|
||||
let _p = port;
|
||||
};
|
||||
let _chan = chan;
|
||||
thread.join();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_thread_send_close_stress() {
|
||||
test!(fn oneshot_multi_thread_close_stress() {
|
||||
stress_factor().times(|| {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
do spawn {
|
||||
let _p = port;
|
||||
}
|
||||
let _chan = chan;
|
||||
})
|
||||
})
|
||||
|
||||
test!(fn oneshot_multi_thread_send_close_stress() {
|
||||
stress_factor().times(|| {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
do spawn {
|
||||
|
@ -1242,10 +1200,9 @@ mod test {
|
|||
chan.send(1);
|
||||
};
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_thread_recv_close_stress() {
|
||||
test!(fn oneshot_multi_thread_recv_close_stress() {
|
||||
stress_factor().times(|| {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
do spawn {
|
||||
|
@ -1262,10 +1219,9 @@ mod test {
|
|||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn oneshot_multi_thread_send_recv_stress() {
|
||||
test!(fn oneshot_multi_thread_send_recv_stress() {
|
||||
stress_factor().times(|| {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
do spawn {
|
||||
|
@ -1275,10 +1231,9 @@ mod test {
|
|||
assert!(port.recv() == ~10);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn stream_send_recv_stress() {
|
||||
test!(fn stream_send_recv_stress() {
|
||||
stress_factor().times(|| {
|
||||
let (port, chan) = Chan::<~int>::new();
|
||||
|
||||
|
@ -1288,7 +1243,7 @@ mod test {
|
|||
fn send(chan: Chan<~int>, i: int) {
|
||||
if i == 10 { return }
|
||||
|
||||
do spawntask_random {
|
||||
do spawn {
|
||||
chan.send(~i);
|
||||
send(chan, i + 1);
|
||||
}
|
||||
|
@ -1297,32 +1252,27 @@ mod test {
|
|||
fn recv(port: Port<~int>, i: int) {
|
||||
if i == 10 { return }
|
||||
|
||||
do spawntask_random {
|
||||
do spawn {
|
||||
assert!(port.recv() == ~i);
|
||||
recv(port, i + 1);
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn recv_a_lot() {
|
||||
test!(fn recv_a_lot() {
|
||||
// Regression test that we don't run out of stack in scheduler context
|
||||
do run_in_newsched_task {
|
||||
let (port, chan) = Chan::new();
|
||||
10000.times(|| { chan.send(()) });
|
||||
10000.times(|| { port.recv() });
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn shared_chan_stress() {
|
||||
do run_in_mt_newsched_task {
|
||||
test!(fn shared_chan_stress() {
|
||||
let (port, chan) = SharedChan::new();
|
||||
let total = stress_factor() + 100;
|
||||
total.times(|| {
|
||||
let chan_clone = chan.clone();
|
||||
do spawntask_random {
|
||||
do spawn {
|
||||
chan_clone.send(());
|
||||
}
|
||||
});
|
||||
|
@ -1330,11 +1280,9 @@ mod test {
|
|||
total.times(|| {
|
||||
port.recv();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn test_nested_recv_iter() {
|
||||
test!(fn test_nested_recv_iter() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
let (total_port, total_chan) = Chan::<int>::new();
|
||||
|
||||
|
@ -1351,10 +1299,9 @@ mod test {
|
|||
chan.send(2);
|
||||
drop(chan);
|
||||
assert_eq!(total_port.recv(), 6);
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn test_recv_iter_break() {
|
||||
test!(fn test_recv_iter_break() {
|
||||
let (port, chan) = Chan::<int>::new();
|
||||
let (count_port, count_chan) = Chan::<int>::new();
|
||||
|
||||
|
@ -1376,5 +1323,5 @@ mod test {
|
|||
chan.try_send(2);
|
||||
drop(chan);
|
||||
assert_eq!(count_port.recv(), 4);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -50,10 +50,13 @@ use kinds::Send;
|
|||
use ops::Drop;
|
||||
use option::{Some, None, Option};
|
||||
use ptr::RawPtr;
|
||||
use super::imp::BlockingContext;
|
||||
use super::{Packet, Port, imp};
|
||||
use result::{Ok, Err};
|
||||
use rt::local::Local;
|
||||
use rt::task::Task;
|
||||
use super::{Packet, Port};
|
||||
use sync::atomics::{Relaxed, SeqCst};
|
||||
use task;
|
||||
use uint;
|
||||
use unstable::atomics::{Relaxed, SeqCst};
|
||||
|
||||
macro_rules! select {
|
||||
(
|
||||
|
@ -184,19 +187,22 @@ impl Select {
|
|||
// Acquire a number of blocking contexts, and block on each one
|
||||
// sequentially until one fails. If one fails, then abort
|
||||
// immediately so we can go unblock on all the other ports.
|
||||
BlockingContext::many(amt, |ctx| {
|
||||
let task: ~Task = Local::take();
|
||||
task.deschedule(amt, |task| {
|
||||
// Prepare for the block
|
||||
let (i, packet) = iter.next().unwrap();
|
||||
assert!((*packet).to_wake.is_none());
|
||||
(*packet).to_wake = Some(task);
|
||||
(*packet).selecting.store(true, SeqCst);
|
||||
if !ctx.block(&mut (*packet).data,
|
||||
&mut (*packet).to_wake,
|
||||
|| (*packet).decrement()) {
|
||||
|
||||
if (*packet).decrement() {
|
||||
Ok(())
|
||||
} else {
|
||||
(*packet).abort_selection(false);
|
||||
(*packet).selecting.store(false, SeqCst);
|
||||
ready_index = i;
|
||||
ready_id = (*packet).selection_id;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
Err((*packet).to_wake.take_unwrap())
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -225,7 +231,7 @@ impl Select {
|
|||
if (*packet).abort_selection(true) {
|
||||
ready_id = (*packet).selection_id;
|
||||
while (*packet).selecting.load(Relaxed) {
|
||||
imp::yield_now();
|
||||
task::deschedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,6 +310,7 @@ impl Iterator<*mut Packet> for PacketIterator {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused_imports)]
|
||||
mod test {
|
||||
use super::super::*;
|
||||
use prelude::*;
|
||||
|
@ -359,19 +366,16 @@ mod test {
|
|||
)
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn unblocks() {
|
||||
use std::io::timer;
|
||||
|
||||
test!(fn unblocks() {
|
||||
let (mut p1, c1) = Chan::<int>::new();
|
||||
let (mut p2, _c2) = Chan::<int>::new();
|
||||
let (p3, c3) = Chan::<int>::new();
|
||||
|
||||
do spawn {
|
||||
timer::sleep(3);
|
||||
20.times(task::deschedule);
|
||||
c1.send(1);
|
||||
p3.recv();
|
||||
timer::sleep(3);
|
||||
20.times(task::deschedule);
|
||||
}
|
||||
|
||||
select! (
|
||||
|
@ -383,18 +387,15 @@ mod test {
|
|||
a = p1.recv_opt() => { assert_eq!(a, None); },
|
||||
_b = p2.recv() => { fail!() }
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn both_ready() {
|
||||
use std::io::timer;
|
||||
})
|
||||
|
||||
test!(fn both_ready() {
|
||||
let (mut p1, c1) = Chan::<int>::new();
|
||||
let (mut p2, c2) = Chan::<int>::new();
|
||||
let (p3, c3) = Chan::<()>::new();
|
||||
|
||||
do spawn {
|
||||
timer::sleep(3);
|
||||
20.times(task::deschedule);
|
||||
c1.send(1);
|
||||
c2.send(2);
|
||||
p3.recv();
|
||||
|
@ -408,11 +409,12 @@ mod test {
|
|||
a = p1.recv() => { assert_eq!(a, 1); },
|
||||
a = p2.recv() => { assert_eq!(a, 2); }
|
||||
)
|
||||
assert_eq!(p1.try_recv(), None);
|
||||
assert_eq!(p2.try_recv(), None);
|
||||
c3.send(());
|
||||
}
|
||||
})
|
||||
|
||||
#[test]
|
||||
fn stress() {
|
||||
test!(fn stress() {
|
||||
static AMT: int = 10000;
|
||||
let (mut p1, c1) = Chan::<int>::new();
|
||||
let (mut p2, c2) = Chan::<int>::new();
|
||||
|
@ -436,69 +438,5 @@ mod test {
|
|||
)
|
||||
c3.send(());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#11003)
|
||||
fn stress_native() {
|
||||
use std::rt::thread::Thread;
|
||||
use std::unstable::run_in_bare_thread;
|
||||
static AMT: int = 10000;
|
||||
|
||||
do run_in_bare_thread {
|
||||
let (mut p1, c1) = Chan::<int>::new();
|
||||
let (mut p2, c2) = Chan::<int>::new();
|
||||
let (p3, c3) = Chan::<()>::new();
|
||||
|
||||
let t = do Thread::start {
|
||||
for i in range(0, AMT) {
|
||||
if i % 2 == 0 {
|
||||
c1.send(i);
|
||||
} else {
|
||||
c2.send(i);
|
||||
}
|
||||
p3.recv();
|
||||
}
|
||||
};
|
||||
|
||||
for i in range(0, AMT) {
|
||||
select! (
|
||||
i1 = p1.recv() => { assert!(i % 2 == 0 && i == i1); },
|
||||
i2 = p2.recv() => { assert!(i % 2 == 1 && i == i2); }
|
||||
)
|
||||
c3.send(());
|
||||
}
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore(cfg(windows))] // FIXME(#11003)
|
||||
fn native_both_ready() {
|
||||
use std::rt::thread::Thread;
|
||||
use std::unstable::run_in_bare_thread;
|
||||
|
||||
do run_in_bare_thread {
|
||||
let (mut p1, c1) = Chan::<int>::new();
|
||||
let (mut p2, c2) = Chan::<int>::new();
|
||||
let (p3, c3) = Chan::<()>::new();
|
||||
|
||||
let t = do Thread::start {
|
||||
c1.send(1);
|
||||
c2.send(2);
|
||||
p3.recv();
|
||||
};
|
||||
|
||||
select! (
|
||||
a = p1.recv() => { assert_eq!(a, 1); },
|
||||
b = p2.recv() => { assert_eq!(b, 2); }
|
||||
)
|
||||
select! (
|
||||
a = p1.recv() => { assert_eq!(a, 1); },
|
||||
b = p2.recv() => { assert_eq!(b, 2); }
|
||||
)
|
||||
c3.send(());
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ use super::{SeekStyle, Read, Write, Open, IoError, Truncate,
|
|||
use rt::rtio::{RtioFileStream, IoFactory, LocalIo};
|
||||
use io;
|
||||
use option::{Some, None, Option};
|
||||
use result::{Ok, Err, Result};
|
||||
use result::{Ok, Err};
|
||||
use path;
|
||||
use path::{Path, GenericPath};
|
||||
use vec::{OwnedVector, ImmutableVector};
|
||||
|
@ -75,17 +75,6 @@ pub struct File {
|
|||
priv last_nread: int,
|
||||
}
|
||||
|
||||
fn io_raise<T>(f: |io: &mut IoFactory| -> Result<T, IoError>) -> Option<T> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match f(io.get()) {
|
||||
Ok(t) => Some(t),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// Open a file at `path` in the mode specified by the `mode` and `access`
|
||||
/// arguments
|
||||
|
@ -131,18 +120,15 @@ impl File {
|
|||
pub fn open_mode(path: &Path,
|
||||
mode: FileMode,
|
||||
access: FileAccess) -> Option<File> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().fs_open(&path.to_c_str(), mode, access) {
|
||||
Ok(fd) => Some(File {
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.fs_open(&path.to_c_str(), mode, access).map(|fd| {
|
||||
File {
|
||||
path: path.clone(),
|
||||
fd: fd,
|
||||
last_nread: -1
|
||||
}),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to open a file in read-only mode. This function is equivalent to
|
||||
|
@ -242,7 +228,7 @@ impl File {
|
|||
/// directory, the user lacks permissions to remove the file, or if some
|
||||
/// other filesystem-level error occurs.
|
||||
pub fn unlink(path: &Path) {
|
||||
io_raise(|io| io.fs_unlink(&path.to_c_str()));
|
||||
LocalIo::maybe_raise(|io| io.fs_unlink(&path.to_c_str()));
|
||||
}
|
||||
|
||||
/// Given a path, query the file system to get information about a file,
|
||||
|
@ -270,7 +256,9 @@ pub fn unlink(path: &Path) {
|
|||
/// requisite permissions to perform a `stat` call on the given path or if
|
||||
/// there is no entry in the filesystem at the provided path.
|
||||
pub fn stat(path: &Path) -> FileStat {
|
||||
io_raise(|io| io.fs_stat(&path.to_c_str())).unwrap_or_else(dummystat)
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.fs_stat(&path.to_c_str())
|
||||
}).unwrap_or_else(dummystat)
|
||||
}
|
||||
|
||||
fn dummystat() -> FileStat {
|
||||
|
@ -306,7 +294,9 @@ fn dummystat() -> FileStat {
|
|||
///
|
||||
/// See `stat`
|
||||
pub fn lstat(path: &Path) -> FileStat {
|
||||
io_raise(|io| io.fs_lstat(&path.to_c_str())).unwrap_or_else(dummystat)
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.fs_lstat(&path.to_c_str())
|
||||
}).unwrap_or_else(dummystat)
|
||||
}
|
||||
|
||||
/// Rename a file or directory to a new name.
|
||||
|
@ -324,7 +314,7 @@ pub fn lstat(path: &Path) -> FileStat {
|
|||
/// the process lacks permissions to view the contents, or if some other
|
||||
/// intermittent I/O error occurs.
|
||||
pub fn rename(from: &Path, to: &Path) {
|
||||
io_raise(|io| io.fs_rename(&from.to_c_str(), &to.to_c_str()));
|
||||
LocalIo::maybe_raise(|io| io.fs_rename(&from.to_c_str(), &to.to_c_str()));
|
||||
}
|
||||
|
||||
/// Copies the contents of one file to another. This function will also
|
||||
|
@ -395,7 +385,7 @@ pub fn copy(from: &Path, to: &Path) {
|
|||
/// condition. Some possible error situations are not having the permission to
|
||||
/// change the attributes of a file or the file not existing.
|
||||
pub fn chmod(path: &Path, mode: io::FilePermission) {
|
||||
io_raise(|io| io.fs_chmod(&path.to_c_str(), mode));
|
||||
LocalIo::maybe_raise(|io| io.fs_chmod(&path.to_c_str(), mode));
|
||||
}
|
||||
|
||||
/// Change the user and group owners of a file at the specified path.
|
||||
|
@ -404,7 +394,7 @@ pub fn chmod(path: &Path, mode: io::FilePermission) {
|
|||
///
|
||||
/// This function will raise on the `io_error` condition on failure.
|
||||
pub fn chown(path: &Path, uid: int, gid: int) {
|
||||
io_raise(|io| io.fs_chown(&path.to_c_str(), uid, gid));
|
||||
LocalIo::maybe_raise(|io| io.fs_chown(&path.to_c_str(), uid, gid));
|
||||
}
|
||||
|
||||
/// Creates a new hard link on the filesystem. The `dst` path will be a
|
||||
|
@ -415,7 +405,7 @@ pub fn chown(path: &Path, uid: int, gid: int) {
|
|||
///
|
||||
/// This function will raise on the `io_error` condition on failure.
|
||||
pub fn link(src: &Path, dst: &Path) {
|
||||
io_raise(|io| io.fs_link(&src.to_c_str(), &dst.to_c_str()));
|
||||
LocalIo::maybe_raise(|io| io.fs_link(&src.to_c_str(), &dst.to_c_str()));
|
||||
}
|
||||
|
||||
/// Creates a new symbolic link on the filesystem. The `dst` path will be a
|
||||
|
@ -425,7 +415,7 @@ pub fn link(src: &Path, dst: &Path) {
|
|||
///
|
||||
/// This function will raise on the `io_error` condition on failure.
|
||||
pub fn symlink(src: &Path, dst: &Path) {
|
||||
io_raise(|io| io.fs_symlink(&src.to_c_str(), &dst.to_c_str()));
|
||||
LocalIo::maybe_raise(|io| io.fs_symlink(&src.to_c_str(), &dst.to_c_str()));
|
||||
}
|
||||
|
||||
/// Reads a symlink, returning the file that the symlink points to.
|
||||
|
@ -436,7 +426,7 @@ pub fn symlink(src: &Path, dst: &Path) {
|
|||
/// conditions include reading a file that does not exist or reading a file
|
||||
/// which is not a symlink.
|
||||
pub fn readlink(path: &Path) -> Option<Path> {
|
||||
io_raise(|io| io.fs_readlink(&path.to_c_str()))
|
||||
LocalIo::maybe_raise(|io| io.fs_readlink(&path.to_c_str()))
|
||||
}
|
||||
|
||||
/// Create a new, empty directory at the provided path
|
||||
|
@ -456,7 +446,7 @@ pub fn readlink(path: &Path) -> Option<Path> {
|
|||
/// to make a new directory at the provided path, or if the directory already
|
||||
/// exists.
|
||||
pub fn mkdir(path: &Path, mode: FilePermission) {
|
||||
io_raise(|io| io.fs_mkdir(&path.to_c_str(), mode));
|
||||
LocalIo::maybe_raise(|io| io.fs_mkdir(&path.to_c_str(), mode));
|
||||
}
|
||||
|
||||
/// Remove an existing, empty directory
|
||||
|
@ -475,7 +465,7 @@ pub fn mkdir(path: &Path, mode: FilePermission) {
|
|||
/// to remove the directory at the provided path, or if the directory isn't
|
||||
/// empty.
|
||||
pub fn rmdir(path: &Path) {
|
||||
io_raise(|io| io.fs_rmdir(&path.to_c_str()));
|
||||
LocalIo::maybe_raise(|io| io.fs_rmdir(&path.to_c_str()));
|
||||
}
|
||||
|
||||
/// Retrieve a vector containing all entries within a provided directory
|
||||
|
@ -502,7 +492,9 @@ pub fn rmdir(path: &Path) {
|
|||
/// the process lacks permissions to view the contents or if the `path` points
|
||||
/// at a non-directory file
|
||||
pub fn readdir(path: &Path) -> ~[Path] {
|
||||
io_raise(|io| io.fs_readdir(&path.to_c_str(), 0)).unwrap_or_else(|| ~[])
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.fs_readdir(&path.to_c_str(), 0)
|
||||
}).unwrap_or_else(|| ~[])
|
||||
}
|
||||
|
||||
/// Returns an iterator which will recursively walk the directory structure
|
||||
|
@ -583,7 +575,7 @@ pub fn rmdir_recursive(path: &Path) {
|
|||
/// happens.
|
||||
// FIXME(#10301) these arguments should not be u64
|
||||
pub fn change_file_times(path: &Path, atime: u64, mtime: u64) {
|
||||
io_raise(|io| io.fs_utime(&path.to_c_str(), atime, mtime));
|
||||
LocalIo::maybe_raise(|io| io.fs_utime(&path.to_c_str(), atime, mtime));
|
||||
}
|
||||
|
||||
impl Reader for File {
|
||||
|
@ -722,7 +714,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
fn tmpdir() -> TempDir {
|
||||
pub fn tmpdir() -> TempDir {
|
||||
use os;
|
||||
use rand;
|
||||
let ret = os::tmpdir().join(format!("rust-{}", rand::random::<u32>()));
|
||||
|
@ -730,32 +722,7 @@ mod test {
|
|||
TempDir(ret)
|
||||
}
|
||||
|
||||
macro_rules! test (
|
||||
{ fn $name:ident() $b:block } => (
|
||||
mod $name {
|
||||
use prelude::*;
|
||||
use io::{SeekSet, SeekCur, SeekEnd, io_error, Read, Open,
|
||||
ReadWrite};
|
||||
use io;
|
||||
use str;
|
||||
use io::fs::{File, rmdir, mkdir, readdir, rmdir_recursive,
|
||||
mkdir_recursive, copy, unlink, stat, symlink, link,
|
||||
readlink, chmod, lstat, change_file_times};
|
||||
use io::fs::test::tmpdir;
|
||||
use util;
|
||||
|
||||
fn f() $b
|
||||
|
||||
#[test] fn uv() { f() }
|
||||
#[test] fn native() {
|
||||
use rt::test::run_in_newsched_task;
|
||||
run_in_newsched_task(f);
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
test!(fn file_test_io_smoke_test() {
|
||||
iotest!(fn file_test_io_smoke_test() {
|
||||
let message = "it's alright. have a good time";
|
||||
let tmpdir = tmpdir();
|
||||
let filename = &tmpdir.join("file_rt_io_file_test.txt");
|
||||
|
@ -775,7 +742,7 @@ mod test {
|
|||
unlink(filename);
|
||||
})
|
||||
|
||||
test!(fn invalid_path_raises() {
|
||||
iotest!(fn invalid_path_raises() {
|
||||
let tmpdir = tmpdir();
|
||||
let filename = &tmpdir.join("file_that_does_not_exist.txt");
|
||||
let mut called = false;
|
||||
|
@ -788,7 +755,7 @@ mod test {
|
|||
assert!(called);
|
||||
})
|
||||
|
||||
test!(fn file_test_iounlinking_invalid_path_should_raise_condition() {
|
||||
iotest!(fn file_test_iounlinking_invalid_path_should_raise_condition() {
|
||||
let tmpdir = tmpdir();
|
||||
let filename = &tmpdir.join("file_another_file_that_does_not_exist.txt");
|
||||
let mut called = false;
|
||||
|
@ -798,7 +765,7 @@ mod test {
|
|||
assert!(called);
|
||||
})
|
||||
|
||||
test!(fn file_test_io_non_positional_read() {
|
||||
iotest!(fn file_test_io_non_positional_read() {
|
||||
let message: &str = "ten-four";
|
||||
let mut read_mem = [0, .. 8];
|
||||
let tmpdir = tmpdir();
|
||||
|
@ -823,7 +790,7 @@ mod test {
|
|||
assert_eq!(read_str, message);
|
||||
})
|
||||
|
||||
test!(fn file_test_io_seek_and_tell_smoke_test() {
|
||||
iotest!(fn file_test_io_seek_and_tell_smoke_test() {
|
||||
let message = "ten-four";
|
||||
let mut read_mem = [0, .. 4];
|
||||
let set_cursor = 4 as u64;
|
||||
|
@ -849,7 +816,7 @@ mod test {
|
|||
assert_eq!(tell_pos_post_read, message.len() as u64);
|
||||
})
|
||||
|
||||
test!(fn file_test_io_seek_and_write() {
|
||||
iotest!(fn file_test_io_seek_and_write() {
|
||||
let initial_msg = "food-is-yummy";
|
||||
let overwrite_msg = "-the-bar!!";
|
||||
let final_msg = "foo-the-bar!!";
|
||||
|
@ -872,7 +839,7 @@ mod test {
|
|||
assert!(read_str == final_msg.to_owned());
|
||||
})
|
||||
|
||||
test!(fn file_test_io_seek_shakedown() {
|
||||
iotest!(fn file_test_io_seek_shakedown() {
|
||||
use std::str; // 01234567890123
|
||||
let initial_msg = "qwer-asdf-zxcv";
|
||||
let chunk_one: &str = "qwer";
|
||||
|
@ -903,7 +870,7 @@ mod test {
|
|||
unlink(filename);
|
||||
})
|
||||
|
||||
test!(fn file_test_stat_is_correct_on_is_file() {
|
||||
iotest!(fn file_test_stat_is_correct_on_is_file() {
|
||||
let tmpdir = tmpdir();
|
||||
let filename = &tmpdir.join("file_stat_correct_on_is_file.txt");
|
||||
{
|
||||
|
@ -916,7 +883,7 @@ mod test {
|
|||
unlink(filename);
|
||||
})
|
||||
|
||||
test!(fn file_test_stat_is_correct_on_is_dir() {
|
||||
iotest!(fn file_test_stat_is_correct_on_is_dir() {
|
||||
let tmpdir = tmpdir();
|
||||
let filename = &tmpdir.join("file_stat_correct_on_is_dir");
|
||||
mkdir(filename, io::UserRWX);
|
||||
|
@ -925,7 +892,7 @@ mod test {
|
|||
rmdir(filename);
|
||||
})
|
||||
|
||||
test!(fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() {
|
||||
iotest!(fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() {
|
||||
let tmpdir = tmpdir();
|
||||
let dir = &tmpdir.join("fileinfo_false_on_dir");
|
||||
mkdir(dir, io::UserRWX);
|
||||
|
@ -933,7 +900,7 @@ mod test {
|
|||
rmdir(dir);
|
||||
})
|
||||
|
||||
test!(fn file_test_fileinfo_check_exists_before_and_after_file_creation() {
|
||||
iotest!(fn file_test_fileinfo_check_exists_before_and_after_file_creation() {
|
||||
let tmpdir = tmpdir();
|
||||
let file = &tmpdir.join("fileinfo_check_exists_b_and_a.txt");
|
||||
File::create(file).write(bytes!("foo"));
|
||||
|
@ -942,7 +909,7 @@ mod test {
|
|||
assert!(!file.exists());
|
||||
})
|
||||
|
||||
test!(fn file_test_directoryinfo_check_exists_before_and_after_mkdir() {
|
||||
iotest!(fn file_test_directoryinfo_check_exists_before_and_after_mkdir() {
|
||||
let tmpdir = tmpdir();
|
||||
let dir = &tmpdir.join("before_and_after_dir");
|
||||
assert!(!dir.exists());
|
||||
|
@ -953,7 +920,7 @@ mod test {
|
|||
assert!(!dir.exists());
|
||||
})
|
||||
|
||||
test!(fn file_test_directoryinfo_readdir() {
|
||||
iotest!(fn file_test_directoryinfo_readdir() {
|
||||
use std::str;
|
||||
let tmpdir = tmpdir();
|
||||
let dir = &tmpdir.join("di_readdir");
|
||||
|
@ -984,11 +951,11 @@ mod test {
|
|||
rmdir(dir);
|
||||
})
|
||||
|
||||
test!(fn recursive_mkdir_slash() {
|
||||
iotest!(fn recursive_mkdir_slash() {
|
||||
mkdir_recursive(&Path::new("/"), io::UserRWX);
|
||||
})
|
||||
|
||||
test!(fn unicode_path_is_dir() {
|
||||
iotest!(fn unicode_path_is_dir() {
|
||||
assert!(Path::new(".").is_dir());
|
||||
assert!(!Path::new("test/stdtest/fs.rs").is_dir());
|
||||
|
||||
|
@ -1006,7 +973,7 @@ mod test {
|
|||
assert!(filepath.exists());
|
||||
})
|
||||
|
||||
test!(fn unicode_path_exists() {
|
||||
iotest!(fn unicode_path_exists() {
|
||||
assert!(Path::new(".").exists());
|
||||
assert!(!Path::new("test/nonexistent-bogus-path").exists());
|
||||
|
||||
|
@ -1018,7 +985,7 @@ mod test {
|
|||
assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists());
|
||||
})
|
||||
|
||||
test!(fn copy_file_does_not_exist() {
|
||||
iotest!(fn copy_file_does_not_exist() {
|
||||
let from = Path::new("test/nonexistent-bogus-path");
|
||||
let to = Path::new("test/other-bogus-path");
|
||||
match io::result(|| copy(&from, &to)) {
|
||||
|
@ -1030,7 +997,7 @@ mod test {
|
|||
}
|
||||
})
|
||||
|
||||
test!(fn copy_file_ok() {
|
||||
iotest!(fn copy_file_ok() {
|
||||
let tmpdir = tmpdir();
|
||||
let input = tmpdir.join("in.txt");
|
||||
let out = tmpdir.join("out.txt");
|
||||
|
@ -1043,7 +1010,7 @@ mod test {
|
|||
assert_eq!(input.stat().perm, out.stat().perm);
|
||||
})
|
||||
|
||||
test!(fn copy_file_dst_dir() {
|
||||
iotest!(fn copy_file_dst_dir() {
|
||||
let tmpdir = tmpdir();
|
||||
let out = tmpdir.join("out");
|
||||
|
||||
|
@ -1053,7 +1020,7 @@ mod test {
|
|||
}
|
||||
})
|
||||
|
||||
test!(fn copy_file_dst_exists() {
|
||||
iotest!(fn copy_file_dst_exists() {
|
||||
let tmpdir = tmpdir();
|
||||
let input = tmpdir.join("in");
|
||||
let output = tmpdir.join("out");
|
||||
|
@ -1066,7 +1033,7 @@ mod test {
|
|||
(bytes!("foo")).to_owned());
|
||||
})
|
||||
|
||||
test!(fn copy_file_src_dir() {
|
||||
iotest!(fn copy_file_src_dir() {
|
||||
let tmpdir = tmpdir();
|
||||
let out = tmpdir.join("out");
|
||||
|
||||
|
@ -1076,7 +1043,7 @@ mod test {
|
|||
assert!(!out.exists());
|
||||
})
|
||||
|
||||
test!(fn copy_file_preserves_perm_bits() {
|
||||
iotest!(fn copy_file_preserves_perm_bits() {
|
||||
let tmpdir = tmpdir();
|
||||
let input = tmpdir.join("in.txt");
|
||||
let out = tmpdir.join("out.txt");
|
||||
|
@ -1091,7 +1058,7 @@ mod test {
|
|||
})
|
||||
|
||||
#[cfg(not(windows))] // FIXME(#10264) operation not permitted?
|
||||
test!(fn symlinks_work() {
|
||||
iotest!(fn symlinks_work() {
|
||||
let tmpdir = tmpdir();
|
||||
let input = tmpdir.join("in.txt");
|
||||
let out = tmpdir.join("out.txt");
|
||||
|
@ -1106,14 +1073,14 @@ mod test {
|
|||
})
|
||||
|
||||
#[cfg(not(windows))] // apparently windows doesn't like symlinks
|
||||
test!(fn symlink_noexist() {
|
||||
iotest!(fn symlink_noexist() {
|
||||
let tmpdir = tmpdir();
|
||||
// symlinks can point to things that don't exist
|
||||
symlink(&tmpdir.join("foo"), &tmpdir.join("bar"));
|
||||
assert!(readlink(&tmpdir.join("bar")).unwrap() == tmpdir.join("foo"));
|
||||
})
|
||||
|
||||
test!(fn readlink_not_symlink() {
|
||||
iotest!(fn readlink_not_symlink() {
|
||||
let tmpdir = tmpdir();
|
||||
match io::result(|| readlink(&*tmpdir)) {
|
||||
Ok(..) => fail!("wanted a failure"),
|
||||
|
@ -1121,7 +1088,7 @@ mod test {
|
|||
}
|
||||
})
|
||||
|
||||
test!(fn links_work() {
|
||||
iotest!(fn links_work() {
|
||||
let tmpdir = tmpdir();
|
||||
let input = tmpdir.join("in.txt");
|
||||
let out = tmpdir.join("out.txt");
|
||||
|
@ -1147,7 +1114,7 @@ mod test {
|
|||
}
|
||||
})
|
||||
|
||||
test!(fn chmod_works() {
|
||||
iotest!(fn chmod_works() {
|
||||
let tmpdir = tmpdir();
|
||||
let file = tmpdir.join("in.txt");
|
||||
|
||||
|
@ -1164,7 +1131,7 @@ mod test {
|
|||
chmod(&file, io::UserFile);
|
||||
})
|
||||
|
||||
test!(fn sync_doesnt_kill_anything() {
|
||||
iotest!(fn sync_doesnt_kill_anything() {
|
||||
let tmpdir = tmpdir();
|
||||
let path = tmpdir.join("in.txt");
|
||||
|
||||
|
@ -1177,7 +1144,7 @@ mod test {
|
|||
drop(file);
|
||||
})
|
||||
|
||||
test!(fn truncate_works() {
|
||||
iotest!(fn truncate_works() {
|
||||
let tmpdir = tmpdir();
|
||||
let path = tmpdir.join("in.txt");
|
||||
|
||||
|
@ -1208,7 +1175,7 @@ mod test {
|
|||
drop(file);
|
||||
})
|
||||
|
||||
test!(fn open_flavors() {
|
||||
iotest!(fn open_flavors() {
|
||||
let tmpdir = tmpdir();
|
||||
|
||||
match io::result(|| File::open_mode(&tmpdir.join("a"), io::Open,
|
||||
|
|
|
@ -164,9 +164,6 @@ requests are implemented by descheduling the running task and
|
|||
performing an asynchronous request; the task is only resumed once the
|
||||
asynchronous request completes.
|
||||
|
||||
For blocking (but possibly more efficient) implementations, look
|
||||
in the `io::native` module.
|
||||
|
||||
# Error Handling
|
||||
|
||||
I/O is an area where nearly every operation can result in unexpected
|
||||
|
@ -316,6 +313,9 @@ pub use self::net::udp::UdpStream;
|
|||
pub use self::pipe::PipeStream;
|
||||
pub use self::process::Process;
|
||||
|
||||
/// Various utility functions useful for writing I/O tests
|
||||
pub mod test;
|
||||
|
||||
/// Synchronous, non-blocking filesystem operations.
|
||||
pub mod fs;
|
||||
|
||||
|
@ -349,8 +349,6 @@ pub mod timer;
|
|||
/// Buffered I/O wrappers
|
||||
pub mod buffered;
|
||||
|
||||
pub mod native;
|
||||
|
||||
/// Signal handling
|
||||
pub mod signal;
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@ getaddrinfo()
|
|||
*/
|
||||
|
||||
use option::{Option, Some, None};
|
||||
use result::{Ok, Err};
|
||||
use io::{io_error};
|
||||
use io::net::ip::{SocketAddr, IpAddr};
|
||||
use rt::rtio::{IoFactory, LocalIo};
|
||||
use vec::ImmutableVector;
|
||||
|
@ -97,14 +95,7 @@ pub fn get_host_addresses(host: &str) -> Option<~[IpAddr]> {
|
|||
/// consumption just yet.
|
||||
fn lookup(hostname: Option<&str>, servname: Option<&str>, hint: Option<Hint>)
|
||||
-> Option<~[Info]> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().get_host_addresses(hostname, servname, hint) {
|
||||
Ok(i) => Some(i),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| io.get_host_addresses(hostname, servname, hint))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -26,17 +26,9 @@ impl TcpStream {
|
|||
}
|
||||
|
||||
pub fn connect(addr: SocketAddr) -> Option<TcpStream> {
|
||||
let result = {
|
||||
let mut io = LocalIo::borrow();
|
||||
io.get().tcp_connect(addr)
|
||||
};
|
||||
match result {
|
||||
Ok(s) => Some(TcpStream::new(s)),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.tcp_connect(addr).map(TcpStream::new)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn peer_name(&mut self) -> Option<SocketAddr> {
|
||||
|
@ -94,14 +86,9 @@ pub struct TcpListener {
|
|||
|
||||
impl TcpListener {
|
||||
pub fn bind(addr: SocketAddr) -> Option<TcpListener> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().tcp_bind(addr) {
|
||||
Ok(l) => Some(TcpListener { obj: l }),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.tcp_bind(addr).map(|l| TcpListener { obj: l })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn socket_name(&mut self) -> Option<SocketAddr> {
|
||||
|
@ -147,14 +134,13 @@ impl Acceptor<TcpStream> for TcpAcceptor {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
use io::net::ip::{Ipv4Addr, SocketAddr};
|
||||
use io::*;
|
||||
use io::test::{next_test_ip4, next_test_ip6};
|
||||
use prelude::*;
|
||||
|
||||
#[test] #[ignore]
|
||||
fn bind_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut called = false;
|
||||
io_error::cond.trap(|e| {
|
||||
assert!(e.kind == PermissionDenied);
|
||||
|
@ -166,11 +152,9 @@ mod test {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut called = false;
|
||||
io_error::cond.trap(|e| {
|
||||
let expected_error = if cfg!(unix) {
|
||||
|
@ -188,15 +172,18 @@ mod test {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_test_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -205,19 +192,17 @@ mod test {
|
|||
assert!(buf[0] == 99);
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_test_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -226,19 +211,17 @@ mod test {
|
|||
assert!(buf[0] == 99);
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_eof_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -247,19 +230,17 @@ mod test {
|
|||
assert!(nread.is_none());
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_eof_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -268,19 +249,17 @@ mod test {
|
|||
assert!(nread.is_none());
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_eof_twice_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -299,19 +278,17 @@ mod test {
|
|||
})
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_eof_twice_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -330,19 +307,17 @@ mod test {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_close_ip4() {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_close_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -364,19 +339,17 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_close_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
let mut stream = acceptor.accept();
|
||||
|
@ -398,20 +371,20 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
port.recv();
|
||||
let _stream = TcpStream::connect(addr);
|
||||
// Close
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_connect_serial_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
let max = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
max.times(|| {
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
});
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for ref mut stream in acceptor.incoming().take(max) {
|
||||
|
@ -421,22 +394,20 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
port.recv();
|
||||
max.times(|| {
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_connect_serial_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
let max = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
max.times(|| {
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
});
|
||||
}
|
||||
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for ref mut stream in acceptor.incoming().take(max) {
|
||||
|
@ -446,27 +417,18 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
port.recv();
|
||||
max.times(|| {
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
stream.write([99]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_connect_interleaved_greedy_schedule_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
static MAX: int = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for (i, stream) in acceptor.incoming().enumerate().take(MAX as uint) {
|
||||
// Start another task to handle the connection
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut stream = stream;
|
||||
let mut buf = [0];
|
||||
stream.read(buf);
|
||||
|
@ -482,7 +444,7 @@ mod test {
|
|||
fn connect(i: int, addr: SocketAddr) {
|
||||
if i == MAX { return }
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
debug!("connecting");
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
// Connect again before writing
|
||||
|
@ -492,21 +454,19 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_connect_interleaved_greedy_schedule_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
static MAX: int = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
let (port, chan) = Chan::<()>::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for (i, stream) in acceptor.incoming().enumerate().take(MAX as uint) {
|
||||
// Start another task to handle the connection
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut stream = stream;
|
||||
let mut buf = [0];
|
||||
stream.read(buf);
|
||||
|
@ -522,7 +482,7 @@ mod test {
|
|||
fn connect(i: int, addr: SocketAddr) {
|
||||
if i == MAX { return }
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
debug!("connecting");
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
// Connect again before writing
|
||||
|
@ -532,21 +492,19 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_connect_interleaved_lazy_schedule_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip4();
|
||||
static MAX: int = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for stream in acceptor.incoming().take(MAX as uint) {
|
||||
// Start another task to handle the connection
|
||||
do spawntask_later {
|
||||
do spawn {
|
||||
let mut stream = stream;
|
||||
let mut buf = [0];
|
||||
stream.read(buf);
|
||||
|
@ -562,7 +520,7 @@ mod test {
|
|||
fn connect(i: int, addr: SocketAddr) {
|
||||
if i == MAX { return }
|
||||
|
||||
do spawntask_later {
|
||||
do spawn {
|
||||
debug!("connecting");
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
// Connect again before writing
|
||||
|
@ -572,20 +530,18 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn multiple_connect_interleaved_lazy_schedule_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let addr = next_test_ip6();
|
||||
static MAX: int = 10;
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
for stream in acceptor.incoming().take(MAX as uint) {
|
||||
// Start another task to handle the connection
|
||||
do spawntask_later {
|
||||
do spawn {
|
||||
let mut stream = stream;
|
||||
let mut buf = [0];
|
||||
stream.read(buf);
|
||||
|
@ -601,7 +557,7 @@ mod test {
|
|||
fn connect(i: int, addr: SocketAddr) {
|
||||
if i == MAX { return }
|
||||
|
||||
do spawntask_later {
|
||||
do spawn {
|
||||
debug!("connecting");
|
||||
let mut stream = TcpStream::connect(addr);
|
||||
// Connect again before writing
|
||||
|
@ -611,12 +567,9 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn socket_name(addr: SocketAddr) {
|
||||
do run_in_mt_newsched_task {
|
||||
do spawntask {
|
||||
let mut listener = TcpListener::bind(addr).unwrap();
|
||||
|
||||
// Make sure socket_name gives
|
||||
|
@ -624,20 +577,15 @@ mod test {
|
|||
let so_name = listener.socket_name();
|
||||
assert!(so_name.is_some());
|
||||
assert_eq!(addr, so_name.unwrap());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn peer_name(addr: SocketAddr) {
|
||||
do run_in_mt_newsched_task {
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
let mut acceptor = TcpListener::bind(addr).listen();
|
||||
chan.send(());
|
||||
|
||||
acceptor.accept();
|
||||
}
|
||||
|
||||
|
@ -654,7 +602,6 @@ mod test {
|
|||
assert!(peer_name.is_some());
|
||||
assert_eq!(addr, peer_name.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn socket_and_peer_name_ip4() {
|
||||
|
@ -668,5 +615,4 @@ mod test {
|
|||
//peer_name(next_test_ip6());
|
||||
socket_name(next_test_ip6());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,14 +21,9 @@ pub struct UdpSocket {
|
|||
|
||||
impl UdpSocket {
|
||||
pub fn bind(addr: SocketAddr) -> Option<UdpSocket> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().udp_bind(addr) {
|
||||
Ok(s) => Some(UdpSocket { obj: s }),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.udp_bind(addr).map(|s| UdpSocket { obj: s })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn recvfrom(&mut self, buf: &mut [u8]) -> Option<(uint, SocketAddr)> {
|
||||
|
@ -104,14 +99,13 @@ impl Writer for UdpStream {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
use io::net::ip::{Ipv4Addr, SocketAddr};
|
||||
use io::*;
|
||||
use io::test::*;
|
||||
use prelude::*;
|
||||
|
||||
#[test] #[ignore]
|
||||
fn bind_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut called = false;
|
||||
io_error::cond.trap(|e| {
|
||||
assert!(e.kind == PermissionDenied);
|
||||
|
@ -123,16 +117,23 @@ mod test {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn socket_smoke_test_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let server_ip = next_test_ip4();
|
||||
let client_ip = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(ref mut client) => {
|
||||
port.recv();
|
||||
client.sendto([99], server_ip)
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(server_ip) {
|
||||
Some(ref mut server) => {
|
||||
chan.send(());
|
||||
|
@ -150,24 +151,22 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(ref mut client) => {
|
||||
port.recv();
|
||||
client.sendto([99], server_ip)
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn socket_smoke_test_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let server_ip = next_test_ip6();
|
||||
let client_ip = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
let (port, chan) = Chan::<()>::new();
|
||||
|
||||
do spawn {
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(ref mut client) => {
|
||||
port.recv();
|
||||
client.sendto([99], server_ip)
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
|
||||
do spawntask {
|
||||
match UdpSocket::bind(server_ip) {
|
||||
Some(ref mut server) => {
|
||||
chan.send(());
|
||||
|
@ -185,24 +184,24 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(ref mut client) => {
|
||||
port.recv();
|
||||
client.sendto([99], server_ip)
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_smoke_test_ip4() {
|
||||
do run_in_mt_newsched_task {
|
||||
let server_ip = next_test_ip4();
|
||||
let client_ip = next_test_ip4();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(client) => {
|
||||
let client = ~client;
|
||||
let mut stream = client.connect(server_ip);
|
||||
port.recv();
|
||||
stream.write([99]);
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(server_ip) {
|
||||
Some(server) => {
|
||||
let server = ~server;
|
||||
|
@ -221,26 +220,24 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(client) => {
|
||||
let client = ~client;
|
||||
let mut stream = client.connect(server_ip);
|
||||
port.recv();
|
||||
stream.write([99]);
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_smoke_test_ip6() {
|
||||
do run_in_mt_newsched_task {
|
||||
let server_ip = next_test_ip6();
|
||||
let client_ip = next_test_ip6();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(client) => {
|
||||
let client = ~client;
|
||||
let mut stream = client.connect(server_ip);
|
||||
port.recv();
|
||||
stream.write([99]);
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(server_ip) {
|
||||
Some(server) => {
|
||||
let server = ~server;
|
||||
|
@ -259,22 +256,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
match UdpSocket::bind(client_ip) {
|
||||
Some(client) => {
|
||||
let client = ~client;
|
||||
let mut stream = client.connect(server_ip);
|
||||
port.recv();
|
||||
stream.write([99]);
|
||||
}
|
||||
None => fail!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn socket_name(addr: SocketAddr) {
|
||||
do run_in_mt_newsched_task {
|
||||
do spawntask {
|
||||
let server = UdpSocket::bind(addr);
|
||||
|
||||
assert!(server.is_some());
|
||||
|
@ -285,9 +267,6 @@ mod test {
|
|||
let so_name = server.socket_name();
|
||||
assert!(so_name.is_some());
|
||||
assert_eq!(addr, so_name.unwrap());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -59,14 +59,9 @@ impl UnixStream {
|
|||
/// stream.write([1, 2, 3]);
|
||||
///
|
||||
pub fn connect<P: ToCStr>(path: &P) -> Option<UnixStream> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().unix_connect(&path.to_c_str()) {
|
||||
Ok(s) => Some(UnixStream::new(s)),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.unix_connect(&path.to_c_str()).map(UnixStream::new)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,14 +102,9 @@ impl UnixListener {
|
|||
/// }
|
||||
///
|
||||
pub fn bind<P: ToCStr>(path: &P) -> Option<UnixListener> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().unix_bind(&path.to_c_str()) {
|
||||
Ok(s) => Some(UnixListener{ obj: s }),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.unix_bind(&path.to_c_str()).map(|s| UnixListener { obj: s })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,30 +140,26 @@ impl Acceptor<UnixStream> for UnixAcceptor {
|
|||
mod tests {
|
||||
use prelude::*;
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
use io::*;
|
||||
use io::test::*;
|
||||
|
||||
fn smalltest(server: proc(UnixStream), client: proc(UnixStream)) {
|
||||
do run_in_mt_newsched_task {
|
||||
let path1 = next_test_unix();
|
||||
let path2 = path1.clone();
|
||||
let (client, server) = (client, server);
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
client(UnixStream::connect(&path2).unwrap());
|
||||
}
|
||||
|
||||
let mut acceptor = UnixListener::bind(&path1).listen();
|
||||
chan.send(());
|
||||
server(acceptor.accept().unwrap());
|
||||
}
|
||||
|
||||
port.recv();
|
||||
client(UnixStream::connect(&path2).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut called = false;
|
||||
io_error::cond.trap(|e| {
|
||||
assert!(e.kind == PermissionDenied);
|
||||
|
@ -184,14 +170,13 @@ mod tests {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut called = false;
|
||||
io_error::cond.trap(|e| {
|
||||
assert_eq!(e.kind, FileNotFound);
|
||||
assert_eq!(e.kind,
|
||||
if cfg!(windows) {OtherIoError} else {FileNotFound});
|
||||
called = true;
|
||||
}).inside(|| {
|
||||
let stream = UnixStream::connect(&("path/to/nowhere"));
|
||||
|
@ -199,7 +184,6 @@ mod tests {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
|
@ -244,13 +228,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn accept_lots() {
|
||||
do run_in_mt_newsched_task {
|
||||
let times = 10;
|
||||
let path1 = next_test_unix();
|
||||
let path2 = path1.clone();
|
||||
let (port, chan) = Chan::new();
|
||||
|
||||
do spawntask {
|
||||
do spawn {
|
||||
port.recv();
|
||||
times.times(|| {
|
||||
let mut stream = UnixStream::connect(&path2);
|
||||
stream.write([100]);
|
||||
})
|
||||
}
|
||||
|
||||
let mut acceptor = UnixListener::bind(&path1).listen();
|
||||
chan.send(());
|
||||
times.times(|| {
|
||||
|
@ -261,20 +251,10 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
port.recv();
|
||||
times.times(|| {
|
||||
let mut stream = UnixStream::connect(&path2);
|
||||
stream.write([100]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_exists() {
|
||||
do run_in_mt_newsched_task {
|
||||
let path = next_test_unix();
|
||||
let _acceptor = UnixListener::bind(&path).listen();
|
||||
assert!(path.exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,22 +106,18 @@ impl<T, A: Acceptor<T>> Acceptor<T> for Option<A> {
|
|||
mod test {
|
||||
use option::*;
|
||||
use super::super::mem::*;
|
||||
use rt::test::*;
|
||||
use super::super::{PreviousIoError, io_error};
|
||||
|
||||
#[test]
|
||||
fn test_option_writer() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut writer: Option<MemWriter> = Some(MemWriter::new());
|
||||
writer.write([0, 1, 2]);
|
||||
writer.flush();
|
||||
assert_eq!(writer.unwrap().inner(), ~[0, 1, 2]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option_writer_error() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut writer: Option<MemWriter> = None;
|
||||
|
||||
let mut called = false;
|
||||
|
@ -142,18 +138,15 @@ mod test {
|
|||
});
|
||||
assert!(called);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option_reader() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut reader: Option<MemReader> = Some(MemReader::new(~[0, 1, 2, 3]));
|
||||
let mut buf = [0, 0];
|
||||
reader.read(buf);
|
||||
assert_eq!(buf, [0, 1]);
|
||||
assert!(!reader.eof());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_option_reader_error() {
|
||||
|
|
|
@ -14,10 +14,9 @@
|
|||
//! enough so that pipes can be created to child processes.
|
||||
|
||||
use prelude::*;
|
||||
use super::{Reader, Writer};
|
||||
use io::{io_error, EndOfFile};
|
||||
use io::native::file;
|
||||
use rt::rtio::{LocalIo, RtioPipe};
|
||||
use libc;
|
||||
use rt::rtio::{RtioPipe, LocalIo};
|
||||
|
||||
pub struct PipeStream {
|
||||
priv obj: ~RtioPipe,
|
||||
|
@ -43,15 +42,10 @@ impl PipeStream {
|
|||
///
|
||||
/// If the pipe cannot be created, an error will be raised on the
|
||||
/// `io_error` condition.
|
||||
pub fn open(fd: file::fd_t) -> Option<PipeStream> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().pipe_open(fd) {
|
||||
Ok(obj) => Some(PipeStream { obj: obj }),
|
||||
Err(e) => {
|
||||
io_error::cond.raise(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn open(fd: libc::c_int) -> Option<PipeStream> {
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.pipe_open(fd).map(|obj| PipeStream { obj: obj })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(inner: ~RtioPipe) -> PipeStream {
|
||||
|
|
|
@ -119,19 +119,17 @@ impl Process {
|
|||
/// Creates a new pipe initialized, but not bound to any particular
|
||||
/// source/destination
|
||||
pub fn new(config: ProcessConfig) -> Option<Process> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().spawn(config) {
|
||||
Ok((p, io)) => Some(Process{
|
||||
let mut config = Some(config);
|
||||
LocalIo::maybe_raise(|io| {
|
||||
io.spawn(config.take_unwrap()).map(|(p, io)| {
|
||||
Process {
|
||||
handle: p,
|
||||
io: io.move_iter().map(|p|
|
||||
io: io.move_iter().map(|p| {
|
||||
p.map(|p| io::PipeStream::new(p))
|
||||
).collect()
|
||||
}),
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the process id of this child process
|
||||
|
|
|
@ -23,8 +23,7 @@ use clone::Clone;
|
|||
use comm::{Port, SharedChan};
|
||||
use container::{Map, MutableMap};
|
||||
use hashmap;
|
||||
use io::io_error;
|
||||
use result::{Err, Ok};
|
||||
use option::{Some, None};
|
||||
use rt::rtio::{IoFactory, LocalIo, RtioSignal};
|
||||
|
||||
#[repr(int)]
|
||||
|
@ -122,16 +121,14 @@ impl Listener {
|
|||
if self.handles.contains_key(&signum) {
|
||||
return true; // self is already listening to signum, so succeed
|
||||
}
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().signal(signum, self.chan.clone()) {
|
||||
Ok(w) => {
|
||||
self.handles.insert(signum, w);
|
||||
match LocalIo::maybe_raise(|io| {
|
||||
io.signal(signum, self.chan.clone())
|
||||
}) {
|
||||
Some(handle) => {
|
||||
self.handles.insert(signum, handle);
|
||||
true
|
||||
},
|
||||
Err(ioerr) => {
|
||||
io_error::cond.raise(ioerr);
|
||||
false
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,13 +27,13 @@ out.write(bytes!("Hello, world!"));
|
|||
*/
|
||||
|
||||
use fmt;
|
||||
use io::buffered::LineBufferedWriter;
|
||||
use io::{Reader, Writer, io_error, IoError, OtherIoError,
|
||||
standard_error, EndOfFile};
|
||||
use libc;
|
||||
use option::{Option, Some, None};
|
||||
use result::{Ok, Err};
|
||||
use io::buffered::LineBufferedWriter;
|
||||
use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY};
|
||||
use super::{Reader, Writer, io_error, IoError, OtherIoError,
|
||||
standard_error, EndOfFile};
|
||||
|
||||
// And so begins the tale of acquiring a uv handle to a stdio stream on all
|
||||
// platforms in all situations. Our story begins by splitting the world into two
|
||||
|
@ -69,19 +69,12 @@ enum StdSource {
|
|||
}
|
||||
|
||||
fn src<T>(fd: libc::c_int, readable: bool, f: |StdSource| -> T) -> T {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().tty_open(fd, readable) {
|
||||
LocalIo::maybe_raise(|io| {
|
||||
Ok(match io.tty_open(fd, readable) {
|
||||
Ok(tty) => f(TTY(tty)),
|
||||
Err(_) => {
|
||||
// It's not really that desirable if these handles are closed
|
||||
// synchronously, and because they're squirreled away in a task
|
||||
// structure the destructors will be run when the task is
|
||||
// attempted to get destroyed. This means that if we run a
|
||||
// synchronous destructor we'll attempt to do some scheduling
|
||||
// operations which will just result in sadness.
|
||||
f(File(io.get().fs_from_raw_fd(fd, DontClose)))
|
||||
}
|
||||
}
|
||||
Err(_) => f(File(io.fs_from_raw_fd(fd, DontClose))),
|
||||
})
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
/// Creates a new non-blocking handle to the stdin of the current process.
|
||||
|
@ -138,7 +131,17 @@ fn with_task_stdout(f: |&mut Writer|) {
|
|||
}
|
||||
|
||||
None => {
|
||||
let mut io = stdout();
|
||||
struct Stdout;
|
||||
impl Writer for Stdout {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
libc::write(libc::STDOUT_FILENO,
|
||||
data.as_ptr() as *libc::c_void,
|
||||
data.len() as libc::size_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut io = Stdout;
|
||||
f(&mut io as &mut Writer);
|
||||
}
|
||||
}
|
||||
|
@ -304,23 +307,10 @@ impl Writer for StdWriter {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rt::test::run_in_newsched_task;
|
||||
|
||||
#[test]
|
||||
fn smoke_uv() {
|
||||
iotest!(fn smoke() {
|
||||
// Just make sure we can acquire handles
|
||||
stdin();
|
||||
stdout();
|
||||
stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_native() {
|
||||
do run_in_newsched_task {
|
||||
stdin();
|
||||
stdout();
|
||||
stderr();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#[macro_escape];
|
||||
|
||||
use os;
|
||||
use prelude::*;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
use std::io::net::ip::*;
|
||||
use sync::atomics::{AtomicUint, INIT_ATOMIC_UINT, Relaxed};
|
||||
|
||||
macro_rules! iotest (
|
||||
{ fn $name:ident() $b:block } => (
|
||||
mod $name {
|
||||
#[allow(unused_imports)];
|
||||
|
||||
use super::super::*;
|
||||
use super::*;
|
||||
use io;
|
||||
use prelude::*;
|
||||
use io::*;
|
||||
use io::fs::*;
|
||||
use io::net::tcp::*;
|
||||
use io::net::ip::*;
|
||||
use io::net::udp::*;
|
||||
#[cfg(unix)]
|
||||
use io::net::unix::*;
|
||||
use str;
|
||||
use util;
|
||||
|
||||
fn f() $b
|
||||
|
||||
#[test] fn green() { f() }
|
||||
#[test] fn native() {
|
||||
use native;
|
||||
let (p, c) = Chan::new();
|
||||
do native::task::spawn { c.send(f()) }
|
||||
p.recv();
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
/// Get a port number, starting at 9600, for use in tests
|
||||
pub fn next_test_port() -> u16 {
|
||||
static mut next_offset: AtomicUint = INIT_ATOMIC_UINT;
|
||||
unsafe {
|
||||
base_port() + next_offset.fetch_add(1, Relaxed) as u16
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a temporary path which could be the location of a unix socket
|
||||
pub fn next_test_unix() -> Path {
|
||||
if cfg!(unix) {
|
||||
os::tmpdir().join(rand::task_rng().gen_ascii_str(20))
|
||||
} else {
|
||||
Path::new(r"\\.\pipe\" + rand::task_rng().gen_ascii_str(20))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a unique IPv4 localhost:port pair starting at 9600
|
||||
pub fn next_test_ip4() -> SocketAddr {
|
||||
SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: next_test_port() }
|
||||
}
|
||||
|
||||
/// Get a unique IPv6 localhost:port pair starting at 9600
|
||||
pub fn next_test_ip6() -> SocketAddr {
|
||||
SocketAddr { ip: Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1), port: next_test_port() }
|
||||
}
|
||||
|
||||
/*
|
||||
XXX: Welcome to MegaHack City.
|
||||
|
||||
The bots run multiple builds at the same time, and these builds
|
||||
all want to use ports. This function figures out which workspace
|
||||
it is running in and assigns a port range based on it.
|
||||
*/
|
||||
fn base_port() -> u16 {
|
||||
|
||||
let base = 9600u16;
|
||||
let range = 1000u16;
|
||||
|
||||
let bases = [
|
||||
("32-opt", base + range * 1),
|
||||
("32-noopt", base + range * 2),
|
||||
("64-opt", base + range * 3),
|
||||
("64-noopt", base + range * 4),
|
||||
("64-opt-vg", base + range * 5),
|
||||
("all-opt", base + range * 6),
|
||||
("snap3", base + range * 7),
|
||||
("dist", base + range * 8)
|
||||
];
|
||||
|
||||
// FIXME (#9639): This needs to handle non-utf8 paths
|
||||
let path = os::getcwd();
|
||||
let path_s = path.as_str().unwrap();
|
||||
|
||||
let mut final_base = base;
|
||||
|
||||
for &(dir, base) in bases.iter() {
|
||||
if path_s.contains(dir) {
|
||||
final_base = base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return final_base;
|
||||
}
|
||||
|
||||
pub fn raise_fd_limit() {
|
||||
unsafe { darwin_fd_limit::raise_fd_limit() }
|
||||
}
|
||||
|
||||
#[cfg(target_os="macos")]
|
||||
#[allow(non_camel_case_types)]
|
||||
mod darwin_fd_limit {
|
||||
/*!
|
||||
* darwin_fd_limit exists to work around an issue where launchctl on Mac OS X defaults the
|
||||
* rlimit maxfiles to 256/unlimited. The default soft limit of 256 ends up being far too low
|
||||
* for our multithreaded scheduler testing, depending on the number of cores available.
|
||||
*
|
||||
* This fixes issue #7772.
|
||||
*/
|
||||
|
||||
use libc;
|
||||
type rlim_t = libc::uint64_t;
|
||||
struct rlimit {
|
||||
rlim_cur: rlim_t,
|
||||
rlim_max: rlim_t
|
||||
}
|
||||
#[nolink]
|
||||
extern {
|
||||
// name probably doesn't need to be mut, but the C function doesn't specify const
|
||||
fn sysctl(name: *mut libc::c_int, namelen: libc::c_uint,
|
||||
oldp: *mut libc::c_void, oldlenp: *mut libc::size_t,
|
||||
newp: *mut libc::c_void, newlen: libc::size_t) -> libc::c_int;
|
||||
fn getrlimit(resource: libc::c_int, rlp: *mut rlimit) -> libc::c_int;
|
||||
fn setrlimit(resource: libc::c_int, rlp: *rlimit) -> libc::c_int;
|
||||
}
|
||||
static CTL_KERN: libc::c_int = 1;
|
||||
static KERN_MAXFILESPERPROC: libc::c_int = 29;
|
||||
static RLIMIT_NOFILE: libc::c_int = 8;
|
||||
|
||||
pub unsafe fn raise_fd_limit() {
|
||||
// The strategy here is to fetch the current resource limits, read the kern.maxfilesperproc
|
||||
// sysctl value, and bump the soft resource limit for maxfiles up to the sysctl value.
|
||||
use ptr::{to_unsafe_ptr, to_mut_unsafe_ptr, mut_null};
|
||||
use mem::size_of_val;
|
||||
use os::last_os_error;
|
||||
|
||||
// Fetch the kern.maxfilesperproc value
|
||||
let mut mib: [libc::c_int, ..2] = [CTL_KERN, KERN_MAXFILESPERPROC];
|
||||
let mut maxfiles: libc::c_int = 0;
|
||||
let mut size: libc::size_t = size_of_val(&maxfiles) as libc::size_t;
|
||||
if sysctl(to_mut_unsafe_ptr(&mut mib[0]), 2,
|
||||
to_mut_unsafe_ptr(&mut maxfiles) as *mut libc::c_void,
|
||||
to_mut_unsafe_ptr(&mut size),
|
||||
mut_null(), 0) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling sysctl: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the current resource limits
|
||||
let mut rlim = rlimit{rlim_cur: 0, rlim_max: 0};
|
||||
if getrlimit(RLIMIT_NOFILE, to_mut_unsafe_ptr(&mut rlim)) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling getrlimit: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bump the soft limit to the smaller of kern.maxfilesperproc and the hard limit
|
||||
rlim.rlim_cur = ::cmp::min(maxfiles as rlim_t, rlim.rlim_max);
|
||||
|
||||
// Set our newly-increased resource limit
|
||||
if setrlimit(RLIMIT_NOFILE, to_unsafe_ptr(&rlim)) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling setrlimit: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os="macos"))]
|
||||
mod darwin_fd_limit {
|
||||
pub unsafe fn raise_fd_limit() {}
|
||||
}
|
|
@ -39,9 +39,7 @@ loop {
|
|||
*/
|
||||
|
||||
use comm::Port;
|
||||
use option::{Option, Some, None};
|
||||
use result::{Ok, Err};
|
||||
use io::io_error;
|
||||
use option::Option;
|
||||
use rt::rtio::{IoFactory, LocalIo, RtioTimer};
|
||||
|
||||
pub struct Timer {
|
||||
|
@ -60,15 +58,7 @@ impl Timer {
|
|||
/// for a number of milliseconds, or to possibly create channels which will
|
||||
/// get notified after an amount of time has passed.
|
||||
pub fn new() -> Option<Timer> {
|
||||
let mut io = LocalIo::borrow();
|
||||
match io.get().timer_init() {
|
||||
Ok(t) => Some(Timer { obj: t }),
|
||||
Err(ioerr) => {
|
||||
debug!("Timer::init: failed to init: {:?}", ioerr);
|
||||
io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
LocalIo::maybe_raise(|io| io.timer_init().map(|t| Timer { obj: t }))
|
||||
}
|
||||
|
||||
/// Blocks the current task for `msecs` milliseconds.
|
||||
|
@ -108,77 +98,60 @@ impl Timer {
|
|||
mod test {
|
||||
use prelude::*;
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_simple() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
timer.sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_oneshot() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
timer.oneshot(1).recv();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_oneshot_forget() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
timer.oneshot(100000000000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oneshot_twice() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
let port1 = timer.oneshot(10000);
|
||||
let port = timer.oneshot(1);
|
||||
port.recv();
|
||||
assert_eq!(port1.try_recv(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_oneshot_then_sleep() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
let port = timer.oneshot(100000000000);
|
||||
timer.sleep(1); // this should invalidate the port
|
||||
|
||||
assert_eq!(port.try_recv(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_periodic() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
let port = timer.periodic(1);
|
||||
port.recv();
|
||||
port.recv();
|
||||
port.recv();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_periodic_forget() {
|
||||
do run_in_mt_newsched_task {
|
||||
let mut timer = Timer::new().unwrap();
|
||||
timer.periodic(100000000000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_timer_sleep_standalone() {
|
||||
do run_in_mt_newsched_task {
|
||||
sleep(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,13 +65,15 @@
|
|||
// When testing libstd, bring in libuv as the I/O backend so tests can print
|
||||
// things and all of the std::io tests have an I/O interface to run on top
|
||||
// of
|
||||
#[cfg(test)] extern mod rustuv = "rustuv#0.9-pre";
|
||||
#[cfg(test)] extern mod rustuv = "rustuv";
|
||||
#[cfg(test)] extern mod native = "native";
|
||||
#[cfg(test)] extern mod green = "green";
|
||||
|
||||
// Make extra accessible for benchmarking
|
||||
#[cfg(test)] extern mod extra = "extra#0.9-pre";
|
||||
#[cfg(test)] extern mod extra = "extra";
|
||||
|
||||
// Make std testable by not duplicating lang items. See #2912
|
||||
#[cfg(test)] extern mod realstd = "std#0.9-pre";
|
||||
#[cfg(test)] extern mod realstd = "std";
|
||||
#[cfg(test)] pub use kinds = realstd::kinds;
|
||||
#[cfg(test)] pub use ops = realstd::ops;
|
||||
#[cfg(test)] pub use cmp = realstd::cmp;
|
||||
|
@ -159,6 +161,7 @@ pub mod trie;
|
|||
pub mod task;
|
||||
pub mod comm;
|
||||
pub mod local_data;
|
||||
pub mod sync;
|
||||
|
||||
|
||||
/* Runtime and platform support */
|
||||
|
|
|
@ -432,6 +432,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[allow(dead_code)]
|
||||
fn test_tls_overwrite_multiple_types() {
|
||||
static str_key: Key<~str> = &Key;
|
||||
static box_key: Key<@()> = &Key;
|
||||
|
|
|
@ -118,26 +118,16 @@ pub static ERROR: u32 = 1;
|
|||
/// It is not recommended to call this function directly, rather it should be
|
||||
/// invoked through the logging family of macros.
|
||||
pub fn log(_level: u32, args: &fmt::Arguments) {
|
||||
unsafe {
|
||||
let optional_task: Option<*mut Task> = Local::try_unsafe_borrow();
|
||||
match optional_task {
|
||||
Some(local) => {
|
||||
// Lazily initialize the local task's logger
|
||||
match (*local).logger {
|
||||
// Use the available logger if we have one
|
||||
Some(ref mut logger) => { logger.log(args); }
|
||||
None => {
|
||||
let mut logger = StdErrLogger::new();
|
||||
logger.log(args);
|
||||
(*local).logger = Some(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's no local task, then always log to stderr
|
||||
None => {
|
||||
let mut logger = StdErrLogger::new();
|
||||
logger.log(args);
|
||||
}
|
||||
}
|
||||
let mut logger = {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().logger.take()
|
||||
};
|
||||
|
||||
if logger.is_none() {
|
||||
logger = Some(StdErrLogger::new());
|
||||
}
|
||||
logger.get_mut_ref().log(args);
|
||||
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().logger = logger;
|
||||
}
|
||||
|
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
#[allow(missing_doc)];
|
||||
|
||||
#[cfg(unix)]
|
||||
use c_str::CString;
|
||||
use clone::Clone;
|
||||
use container::Container;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -43,8 +41,7 @@ use ptr;
|
|||
use str;
|
||||
use to_str;
|
||||
use unstable::finally::Finally;
|
||||
|
||||
pub use os::consts::*;
|
||||
use sync::atomics::{AtomicInt, INIT_ATOMIC_INT, SeqCst};
|
||||
|
||||
/// Delegates to the libc close() function, returning the same return value.
|
||||
pub fn close(fd: c_int) -> c_int {
|
||||
|
@ -58,6 +55,8 @@ static BUF_BYTES : uint = 2048u;
|
|||
|
||||
#[cfg(unix)]
|
||||
pub fn getcwd() -> Path {
|
||||
use c_str::CString;
|
||||
|
||||
let mut buf = [0 as libc::c_char, ..BUF_BYTES];
|
||||
unsafe {
|
||||
if libc::getcwd(buf.as_mut_ptr(), buf.len() as size_t).is_null() {
|
||||
|
@ -333,7 +332,7 @@ pub fn pipe() -> Pipe {
|
|||
|
||||
/// Returns the proper dll filename for the given basename of a file.
|
||||
pub fn dll_filename(base: &str) -> ~str {
|
||||
format!("{}{}{}", DLL_PREFIX, base, DLL_SUFFIX)
|
||||
format!("{}{}{}", consts::DLL_PREFIX, base, consts::DLL_SUFFIX)
|
||||
}
|
||||
|
||||
/// Optionally returns the filesystem path to the current executable which is
|
||||
|
@ -675,17 +674,26 @@ pub fn last_os_error() -> ~str {
|
|||
strerror()
|
||||
}
|
||||
|
||||
static mut EXIT_STATUS: AtomicInt = INIT_ATOMIC_INT;
|
||||
|
||||
/**
|
||||
* Sets the process exit code
|
||||
*
|
||||
* Sets the exit code returned by the process if all supervised tasks
|
||||
* terminate successfully (without failing). If the current root task fails
|
||||
* and is supervised by the scheduler then any user-specified exit status is
|
||||
* ignored and the process exits with the default failure status
|
||||
* ignored and the process exits with the default failure status.
|
||||
*
|
||||
* Note that this is not synchronized against modifications of other threads.
|
||||
*/
|
||||
pub fn set_exit_status(code: int) {
|
||||
use rt;
|
||||
rt::set_exit_status(code);
|
||||
unsafe { EXIT_STATUS.store(code, SeqCst) }
|
||||
}
|
||||
|
||||
/// Fetches the process's current exit code. This defaults to 0 and can change
|
||||
/// by calling `set_exit_status`.
|
||||
pub fn get_exit_status() -> int {
|
||||
unsafe { EXIT_STATUS.load(SeqCst) }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
|
@ -12,9 +12,8 @@ use c_str::{ToCStr, CString};
|
|||
use libc::{c_char, size_t};
|
||||
use option::{Option, None, Some};
|
||||
use ptr::RawPtr;
|
||||
use rt::env;
|
||||
use rt;
|
||||
use rt::local::Local;
|
||||
use rt::task;
|
||||
use rt::task::Task;
|
||||
use str::OwnedStr;
|
||||
use str;
|
||||
|
@ -62,7 +61,7 @@ unsafe fn fail_borrowed(alloc: *mut raw::Box<()>, file: *c_char, line: size_t)
|
|||
match try_take_task_borrow_list() {
|
||||
None => { // not recording borrows
|
||||
let msg = "borrowed";
|
||||
msg.with_c_str(|msg_p| task::begin_unwind_raw(msg_p, file, line))
|
||||
msg.with_c_str(|msg_p| rt::begin_unwind_raw(msg_p, file, line))
|
||||
}
|
||||
Some(borrow_list) => { // recording borrows
|
||||
let mut msg = ~"borrowed";
|
||||
|
@ -76,7 +75,7 @@ unsafe fn fail_borrowed(alloc: *mut raw::Box<()>, file: *c_char, line: size_t)
|
|||
sep = " and at ";
|
||||
}
|
||||
}
|
||||
msg.with_c_str(|msg_p| task::begin_unwind_raw(msg_p, file, line))
|
||||
msg.with_c_str(|msg_p| rt::begin_unwind_raw(msg_p, file, line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +94,7 @@ unsafe fn debug_borrow<T,P:RawPtr<T>>(tag: &'static str,
|
|||
//! A useful debugging function that prints a pointer + tag + newline
|
||||
//! without allocating memory.
|
||||
|
||||
if ENABLE_DEBUG && env::debug_borrow() {
|
||||
if ENABLE_DEBUG && rt::env::debug_borrow() {
|
||||
debug_borrow_slow(tag, p, old_bits, new_bits, filename, line);
|
||||
}
|
||||
|
||||
|
@ -180,7 +179,7 @@ pub unsafe fn unrecord_borrow(a: *u8,
|
|||
if br.alloc != a || br.file != file || br.line != line {
|
||||
let err = format!("wrong borrow found, br={:?}", br);
|
||||
err.with_c_str(|msg_p| {
|
||||
task::begin_unwind_raw(msg_p, file, line)
|
||||
rt::begin_unwind_raw(msg_p, file, line)
|
||||
})
|
||||
}
|
||||
borrow_list
|
||||
|
|
|
@ -30,7 +30,7 @@ pub struct CrateMap<'a> {
|
|||
version: i32,
|
||||
entries: &'a [ModEntry<'a>],
|
||||
children: &'a [&'a CrateMap<'a>],
|
||||
event_loop_factory: Option<extern "C" fn() -> ~EventLoop>,
|
||||
event_loop_factory: Option<fn() -> ~EventLoop>,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
|
|
@ -17,7 +17,7 @@ use os;
|
|||
// Note that these are all accessed without any synchronization.
|
||||
// They are expected to be initialized once then left alone.
|
||||
|
||||
static mut MIN_STACK: uint = 2000000;
|
||||
static mut MIN_STACK: uint = 2 * 1024 * 1024;
|
||||
static mut DEBUG_BORROW: bool = false;
|
||||
static mut POISON_ON_FREE: bool = false;
|
||||
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
/*!
|
||||
|
||||
Task death: asynchronous killing, linked failure, exit code propagation.
|
||||
|
||||
This file implements two orthogonal building-blocks for communicating failure
|
||||
between tasks. One is 'linked failure' or 'task killing', that is, a failing
|
||||
task causing other tasks to fail promptly (even those that are blocked on
|
||||
pipes or I/O). The other is 'exit code propagation', which affects the result
|
||||
observed by the parent of a task::try task that itself spawns child tasks
|
||||
(such as any #[test] function). In both cases the data structures live in
|
||||
KillHandle.
|
||||
|
||||
|
||||
I. Task killing.
|
||||
|
||||
The model for killing involves two atomic flags, the "kill flag" and the
|
||||
"unkillable flag". Operations on the kill flag include:
|
||||
|
||||
- In the taskgroup code (task/spawn.rs), tasks store a clone of their
|
||||
KillHandle in their shared taskgroup. Another task in the group that fails
|
||||
will use that handle to call kill().
|
||||
- When a task blocks, it turns its ~Task into a BlockedTask by storing a
|
||||
the transmuted ~Task pointer inside the KillHandle's kill flag. A task
|
||||
trying to block and a task trying to kill it can simultaneously access the
|
||||
kill flag, after which the task will get scheduled and fail (no matter who
|
||||
wins the race). Likewise, a task trying to wake a blocked task normally and
|
||||
a task trying to kill it can simultaneously access the flag; only one will
|
||||
get the task to reschedule it.
|
||||
|
||||
Operations on the unkillable flag include:
|
||||
|
||||
- When a task becomes unkillable, it swaps on the flag to forbid any killer
|
||||
from waking it up while it's blocked inside the unkillable section. If a
|
||||
kill was already pending, the task fails instead of becoming unkillable.
|
||||
- When a task is done being unkillable, it restores the flag to the normal
|
||||
running state. If a kill was received-but-blocked during the unkillable
|
||||
section, the task fails at this later point.
|
||||
- When a task tries to kill another task, before swapping on the kill flag, it
|
||||
first swaps on the unkillable flag, to see if it's "allowed" to wake up the
|
||||
task. If it isn't, the killed task will receive the signal when it becomes
|
||||
killable again. (Of course, a task trying to wake the task normally (e.g.
|
||||
sending on a channel) does not access the unkillable flag at all.)
|
||||
|
||||
Why do we not need acquire/release barriers on any of the kill flag swaps?
|
||||
This is because barriers establish orderings between accesses on different
|
||||
memory locations, but each kill-related operation is only a swap on a single
|
||||
location, so atomicity is all that matters. The exception is kill(), which
|
||||
does a swap on both flags in sequence. kill() needs no barriers because it
|
||||
does not matter if its two accesses are seen reordered on another CPU: if a
|
||||
killer does perform both writes, it means it saw a KILL_RUNNING in the
|
||||
unkillable flag, which means an unkillable task will see KILL_KILLED and fail
|
||||
immediately (rendering the subsequent write to the kill flag unnecessary).
|
||||
|
||||
|
||||
II. Exit code propagation.
|
||||
|
||||
The basic model for exit code propagation, which is used with the "watched"
|
||||
spawn mode (on by default for linked spawns, off for supervised and unlinked
|
||||
spawns), is that a parent will wait for all its watched children to exit
|
||||
before reporting whether it succeeded or failed. A watching parent will only
|
||||
report success if it succeeded and all its children also reported success;
|
||||
otherwise, it will report failure. This is most useful for writing test cases:
|
||||
|
||||
```
|
||||
#[test]
|
||||
fn test_something_in_another_task {
|
||||
do spawn {
|
||||
assert!(collatz_conjecture_is_false());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, as the child task will certainly outlive the parent task, we might miss
|
||||
the failure of the child when deciding whether or not the test case passed.
|
||||
The watched spawn mode avoids this problem.
|
||||
|
||||
In order to propagate exit codes from children to their parents, any
|
||||
'watching' parent must wait for all of its children to exit before it can
|
||||
report its final exit status. We achieve this by using an UnsafeArc, using the
|
||||
reference counting to track how many children are still alive, and using the
|
||||
unwrap() operation in the parent's exit path to wait for all children to exit.
|
||||
The UnsafeArc referred to here is actually the KillHandle itself.
|
||||
|
||||
This also works transitively, as if a "middle" watched child task is itself
|
||||
watching a grandchild task, the "middle" task will do unwrap() on its own
|
||||
KillHandle (thereby waiting for the grandchild to exit) before dropping its
|
||||
reference to its watching parent (which will alert the parent).
|
||||
|
||||
While UnsafeArc::unwrap() accomplishes the synchronization, there remains the
|
||||
matter of reporting the exit codes themselves. This is easiest when an exiting
|
||||
watched task has no watched children of its own:
|
||||
|
||||
- If the task with no watched children exits successfully, it need do nothing.
|
||||
- If the task with no watched children has failed, it sets a flag in the
|
||||
parent's KillHandle ("any_child_failed") to false. It then stays false forever.
|
||||
|
||||
However, if a "middle" watched task with watched children of its own exits
|
||||
before its child exits, we need to ensure that the grandparent task may still
|
||||
see a failure from the grandchild task. While we could achieve this by having
|
||||
each intermediate task block on its handle, this keeps around the other resources
|
||||
the task was using. To be more efficient, this is accomplished via "tombstones".
|
||||
|
||||
A tombstone is a closure, proc() -> bool, which will perform any waiting necessary
|
||||
to collect the exit code of descendant tasks. In its environment is captured
|
||||
the KillHandle of whichever task created the tombstone, and perhaps also any
|
||||
tombstones that that task itself had, and finally also another tombstone,
|
||||
effectively creating a lazy-list of heap closures.
|
||||
|
||||
When a child wishes to exit early and leave tombstones behind for its parent,
|
||||
it must use a LittleLock (pthread mutex) to synchronize with any possible
|
||||
sibling tasks which are trying to do the same thing with the same parent.
|
||||
However, on the other side, when the parent is ready to pull on the tombstones,
|
||||
it need not use this lock, because the unwrap() serves as a barrier that ensures
|
||||
no children will remain with references to the handle.
|
||||
|
||||
The main logic for creating and assigning tombstones can be found in the
|
||||
function reparent_children_to() in the impl for KillHandle.
|
||||
|
||||
|
||||
IIA. Issues with exit code propagation.
|
||||
|
||||
There are two known issues with the current scheme for exit code propagation.
|
||||
|
||||
- As documented in issue #8136, the structure mandates the possibility for stack
|
||||
overflow when collecting tombstones that are very deeply nested. This cannot
|
||||
be avoided with the closure representation, as tombstones end up structured in
|
||||
a sort of tree. However, notably, the tombstones do not actually need to be
|
||||
collected in any particular order, and so a doubly-linked list may be used.
|
||||
However we do not do this yet because DList is in libextra.
|
||||
|
||||
- A discussion with Graydon made me realize that if we decoupled the exit code
|
||||
propagation from the parents-waiting action, this could result in a simpler
|
||||
implementation as the exit codes themselves would not have to be propagated,
|
||||
and could instead be propagated implicitly through the taskgroup mechanism
|
||||
that we already have. The tombstoning scheme would still be required. I have
|
||||
not implemented this because currently we can't receive a linked failure kill
|
||||
signal during the task cleanup activity, as that is currently "unkillable",
|
||||
and occurs outside the task's unwinder's "try" block, so would require some
|
||||
restructuring.
|
||||
|
||||
*/
|
||||
|
||||
use cast;
|
||||
use option::{Option, Some, None};
|
||||
use prelude::*;
|
||||
use iter;
|
||||
use task::TaskResult;
|
||||
use rt::task::Task;
|
||||
use unstable::atomics::{AtomicUint, SeqCst};
|
||||
use unstable::sync::UnsafeArc;
|
||||
|
||||
/// A handle to a blocked task. Usually this means having the ~Task pointer by
|
||||
/// ownership, but if the task is killable, a killer can steal it at any time.
|
||||
pub enum BlockedTask {
|
||||
Owned(~Task),
|
||||
Shared(UnsafeArc<AtomicUint>),
|
||||
}
|
||||
|
||||
/// Per-task state related to task death, killing, failure, etc.
|
||||
pub struct Death {
|
||||
// Action to be done with the exit code. If set, also makes the task wait
|
||||
// until all its watched children exit before collecting the status.
|
||||
on_exit: Option<proc(TaskResult)>,
|
||||
// nesting level counter for unstable::atomically calls (0 == can deschedule).
|
||||
priv wont_sleep: int,
|
||||
}
|
||||
|
||||
pub struct BlockedTaskIterator {
|
||||
priv inner: UnsafeArc<AtomicUint>,
|
||||
}
|
||||
|
||||
impl Iterator<BlockedTask> for BlockedTaskIterator {
|
||||
fn next(&mut self) -> Option<BlockedTask> {
|
||||
Some(Shared(self.inner.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockedTask {
|
||||
/// Returns Some if the task was successfully woken; None if already killed.
|
||||
pub fn wake(self) -> Option<~Task> {
|
||||
match self {
|
||||
Owned(task) => Some(task),
|
||||
Shared(arc) => unsafe {
|
||||
match (*arc.get()).swap(0, SeqCst) {
|
||||
0 => None,
|
||||
n => cast::transmute(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a blocked task, unless the task was already killed.
|
||||
pub fn block(task: ~Task) -> BlockedTask {
|
||||
Owned(task)
|
||||
}
|
||||
|
||||
/// Converts one blocked task handle to a list of many handles to the same.
|
||||
pub fn make_selectable(self, num_handles: uint)
|
||||
-> iter::Take<BlockedTaskIterator>
|
||||
{
|
||||
let arc = match self {
|
||||
Owned(task) => {
|
||||
let flag = unsafe { AtomicUint::new(cast::transmute(task)) };
|
||||
UnsafeArc::new(flag)
|
||||
}
|
||||
Shared(arc) => arc.clone(),
|
||||
};
|
||||
BlockedTaskIterator{ inner: arc }.take(num_handles)
|
||||
}
|
||||
|
||||
// This assertion has two flavours because the wake involves an atomic op.
|
||||
// In the faster version, destructors will fail dramatically instead.
|
||||
#[inline] #[cfg(not(test))]
|
||||
pub fn assert_already_awake(self) { }
|
||||
#[inline] #[cfg(test)]
|
||||
pub fn assert_already_awake(self) { assert!(self.wake().is_none()); }
|
||||
|
||||
/// Convert to an unsafe uint value. Useful for storing in a pipe's state flag.
|
||||
#[inline]
|
||||
pub unsafe fn cast_to_uint(self) -> uint {
|
||||
match self {
|
||||
Owned(task) => {
|
||||
let blocked_task_ptr: uint = cast::transmute(task);
|
||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||
blocked_task_ptr
|
||||
}
|
||||
Shared(arc) => {
|
||||
let blocked_task_ptr: uint = cast::transmute(~arc);
|
||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||
blocked_task_ptr | 0x1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from an unsafe uint value. Useful for retrieving a pipe's state flag.
|
||||
#[inline]
|
||||
pub unsafe fn cast_from_uint(blocked_task_ptr: uint) -> BlockedTask {
|
||||
if blocked_task_ptr & 0x1 == 0 {
|
||||
Owned(cast::transmute(blocked_task_ptr))
|
||||
} else {
|
||||
let ptr: ~UnsafeArc<AtomicUint> = cast::transmute(blocked_task_ptr & !1);
|
||||
Shared(*ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Death {
|
||||
pub fn new() -> Death {
|
||||
Death {
|
||||
on_exit: None,
|
||||
wont_sleep: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect failure exit codes from children and propagate them to a parent.
|
||||
pub fn collect_failure(&mut self, result: TaskResult) {
|
||||
match self.on_exit.take() {
|
||||
Some(f) => f(result),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter a possibly-nested "atomic" section of code. Just for assertions.
|
||||
/// All calls must be paired with a subsequent call to allow_deschedule.
|
||||
#[inline]
|
||||
pub fn inhibit_deschedule(&mut self) {
|
||||
self.wont_sleep += 1;
|
||||
}
|
||||
|
||||
/// Exit a possibly-nested "atomic" section of code. Just for assertions.
|
||||
/// All calls must be paired with a preceding call to inhibit_deschedule.
|
||||
#[inline]
|
||||
pub fn allow_deschedule(&mut self) {
|
||||
rtassert!(self.wont_sleep != 0);
|
||||
self.wont_sleep -= 1;
|
||||
}
|
||||
|
||||
/// Ensure that the task is allowed to become descheduled.
|
||||
#[inline]
|
||||
pub fn assert_may_sleep(&self) {
|
||||
if self.wont_sleep != 0 {
|
||||
rtabort!("illegal atomic-sleep: attempt to reschedule while \
|
||||
using an Exclusive or LittleLock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Death {
|
||||
fn drop(&mut self) {
|
||||
// Mustn't be in an atomic or unkillable section at task death.
|
||||
rtassert!(self.wont_sleep == 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rt::test::*;
|
||||
use super::*;
|
||||
|
||||
// Task blocking tests
|
||||
|
||||
#[test]
|
||||
fn block_and_wake() {
|
||||
do with_test_task |task| {
|
||||
BlockedTask::block(task).wake().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,8 +8,7 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use option::{Option, Some, None};
|
||||
use rt::sched::Scheduler;
|
||||
use option::Option;
|
||||
use rt::task::Task;
|
||||
use rt::local_ptr;
|
||||
|
||||
|
@ -46,87 +45,10 @@ impl Local<local_ptr::Borrowed<Task>> for Task {
|
|||
}
|
||||
}
|
||||
|
||||
/// Encapsulates a temporarily-borrowed scheduler.
|
||||
pub struct BorrowedScheduler {
|
||||
priv task: local_ptr::Borrowed<Task>,
|
||||
}
|
||||
|
||||
impl BorrowedScheduler {
|
||||
fn new(mut task: local_ptr::Borrowed<Task>) -> BorrowedScheduler {
|
||||
if task.get().sched.is_none() {
|
||||
rtabort!("no scheduler")
|
||||
} else {
|
||||
BorrowedScheduler {
|
||||
task: task,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<'a>(&'a mut self) -> &'a mut ~Scheduler {
|
||||
match self.task.get().sched {
|
||||
None => rtabort!("no scheduler"),
|
||||
Some(ref mut sched) => sched,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Local<BorrowedScheduler> for Scheduler {
|
||||
fn put(value: ~Scheduler) {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().sched = Some(value);
|
||||
}
|
||||
#[inline]
|
||||
fn take() -> ~Scheduler {
|
||||
unsafe {
|
||||
// XXX: Unsafe for speed
|
||||
let task: *mut Task = Local::unsafe_borrow();
|
||||
(*task).sched.take_unwrap()
|
||||
}
|
||||
}
|
||||
fn exists(_: Option<Scheduler>) -> bool {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
task.get().sched.is_some()
|
||||
}
|
||||
#[inline]
|
||||
fn borrow(_: Option<Scheduler>) -> BorrowedScheduler {
|
||||
BorrowedScheduler::new(Local::borrow(None::<Task>))
|
||||
}
|
||||
unsafe fn unsafe_take() -> ~Scheduler { rtabort!("unimpl") }
|
||||
unsafe fn unsafe_borrow() -> *mut Scheduler {
|
||||
let task: *mut Task = Local::unsafe_borrow();
|
||||
match (*task).sched {
|
||||
Some(~ref mut sched) => {
|
||||
let s: *mut Scheduler = &mut *sched;
|
||||
return s;
|
||||
}
|
||||
None => {
|
||||
rtabort!("no scheduler")
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn try_unsafe_borrow() -> Option<*mut Scheduler> {
|
||||
let task_opt: Option<*mut Task> = Local::try_unsafe_borrow();
|
||||
match task_opt {
|
||||
Some(task) => {
|
||||
match (*task).sched {
|
||||
Some(~ref mut sched) => {
|
||||
let s: *mut Scheduler = &mut *sched;
|
||||
Some(s)
|
||||
}
|
||||
None => None
|
||||
}
|
||||
}
|
||||
None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use option::None;
|
||||
use unstable::run_in_bare_thread;
|
||||
use rt::test::*;
|
||||
use super::*;
|
||||
use rt::task::Task;
|
||||
use rt::local_ptr;
|
||||
|
@ -135,8 +57,7 @@ mod test {
|
|||
fn thread_local_task_smoke_test() {
|
||||
do run_in_bare_thread {
|
||||
local_ptr::init();
|
||||
let mut sched = ~new_test_uv_sched();
|
||||
let task = ~Task::new_root(&mut sched.stack_pool, None, proc(){});
|
||||
let task = ~Task::new();
|
||||
Local::put(task);
|
||||
let task: ~Task = Local::take();
|
||||
cleanup_task(task);
|
||||
|
@ -147,12 +68,11 @@ mod test {
|
|||
fn thread_local_task_two_instances() {
|
||||
do run_in_bare_thread {
|
||||
local_ptr::init();
|
||||
let mut sched = ~new_test_uv_sched();
|
||||
let task = ~Task::new_root(&mut sched.stack_pool, None, proc(){});
|
||||
let task = ~Task::new();
|
||||
Local::put(task);
|
||||
let task: ~Task = Local::take();
|
||||
cleanup_task(task);
|
||||
let task = ~Task::new_root(&mut sched.stack_pool, None, proc(){});
|
||||
let task = ~Task::new();
|
||||
Local::put(task);
|
||||
let task: ~Task = Local::take();
|
||||
cleanup_task(task);
|
||||
|
@ -164,8 +84,7 @@ mod test {
|
|||
fn borrow_smoke_test() {
|
||||
do run_in_bare_thread {
|
||||
local_ptr::init();
|
||||
let mut sched = ~new_test_uv_sched();
|
||||
let task = ~Task::new_root(&mut sched.stack_pool, None, proc(){});
|
||||
let task = ~Task::new();
|
||||
Local::put(task);
|
||||
|
||||
unsafe {
|
||||
|
@ -180,8 +99,7 @@ mod test {
|
|||
fn borrow_with_return() {
|
||||
do run_in_bare_thread {
|
||||
local_ptr::init();
|
||||
let mut sched = ~new_test_uv_sched();
|
||||
let task = ~Task::new_root(&mut sched.stack_pool, None, proc(){});
|
||||
let task = ~Task::new();
|
||||
Local::put(task);
|
||||
|
||||
{
|
||||
|
@ -193,5 +111,9 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
fn cleanup_task(mut t: ~Task) {
|
||||
t.destroyed = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ impl<T> Drop for Borrowed<T> {
|
|||
}
|
||||
let val: ~T = cast::transmute(self.val);
|
||||
put::<T>(val);
|
||||
assert!(exists());
|
||||
rtassert!(exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,9 @@ pub mod compiled {
|
|||
/// Does not validate the pointer type.
|
||||
#[inline]
|
||||
pub unsafe fn take<T>() -> ~T {
|
||||
let ptr: ~T = cast::transmute(RT_TLS_PTR);
|
||||
let ptr = RT_TLS_PTR;
|
||||
rtassert!(!ptr.is_null());
|
||||
let ptr: ~T = cast::transmute(ptr);
|
||||
// can't use `as`, due to type not matching with `cfg(test)`
|
||||
RT_TLS_PTR = cast::transmute(0);
|
||||
ptr
|
||||
|
@ -178,7 +180,7 @@ pub mod native {
|
|||
}
|
||||
|
||||
pub unsafe fn cleanup() {
|
||||
assert!(INITIALIZED);
|
||||
rtassert!(INITIALIZED);
|
||||
tls::destroy(RT_TLS_KEY);
|
||||
LOCK.destroy();
|
||||
INITIALIZED = false;
|
||||
|
|
|
@ -57,27 +57,17 @@ Several modules in `core` are clients of `rt`:
|
|||
// XXX: this should not be here.
|
||||
#[allow(missing_doc)];
|
||||
|
||||
use any::Any;
|
||||
use clone::Clone;
|
||||
use container::Container;
|
||||
use iter::Iterator;
|
||||
use option::{Option, None, Some};
|
||||
use option::Option;
|
||||
use ptr::RawPtr;
|
||||
use rt::local::Local;
|
||||
use rt::sched::{Scheduler, Shutdown};
|
||||
use rt::sleeper_list::SleeperList;
|
||||
use task::TaskResult;
|
||||
use rt::task::{Task, SchedTask, GreenTask, Sched};
|
||||
use send_str::SendStrStatic;
|
||||
use unstable::atomics::{AtomicInt, AtomicBool, SeqCst};
|
||||
use unstable::sync::UnsafeArc;
|
||||
use result::Result;
|
||||
use task::TaskOpts;
|
||||
use vec::{OwnedVector, MutableVector, ImmutableVector};
|
||||
use vec;
|
||||
|
||||
use self::thread::Thread;
|
||||
|
||||
// the os module needs to reach into this helper, so allow general access
|
||||
// through this reexport.
|
||||
pub use self::util::set_exit_status;
|
||||
use self::task::{Task, BlockedTask};
|
||||
|
||||
// this is somewhat useful when a program wants to spawn a "reasonable" number
|
||||
// of workers based on the constraints of the system that it's running on.
|
||||
|
@ -85,8 +75,8 @@ pub use self::util::set_exit_status;
|
|||
// method...
|
||||
pub use self::util::default_sched_threads;
|
||||
|
||||
// Re-export of the functionality in the kill module
|
||||
pub use self::kill::BlockedTask;
|
||||
// Export unwinding facilities used by the failure macros
|
||||
pub use self::unwind::{begin_unwind, begin_unwind_raw};
|
||||
|
||||
// XXX: these probably shouldn't be public...
|
||||
#[doc(hidden)]
|
||||
|
@ -99,21 +89,12 @@ pub mod shouldnt_be_public {
|
|||
// Internal macros used by the runtime.
|
||||
mod macros;
|
||||
|
||||
/// Basic implementation of an EventLoop, provides no I/O interfaces
|
||||
mod basic;
|
||||
|
||||
/// The global (exchange) heap.
|
||||
pub mod global_heap;
|
||||
|
||||
/// Implementations of language-critical runtime features like @.
|
||||
pub mod task;
|
||||
|
||||
/// Facilities related to task failure, killing, and death.
|
||||
mod kill;
|
||||
|
||||
/// The coroutine task scheduler, built on the `io` event loop.
|
||||
pub mod sched;
|
||||
|
||||
/// The EventLoop and internal synchronous I/O interface.
|
||||
pub mod rtio;
|
||||
|
||||
|
@ -121,27 +102,6 @@ pub mod rtio;
|
|||
/// or task-local storage.
|
||||
pub mod local;
|
||||
|
||||
/// A mostly lock-free multi-producer, single consumer queue.
|
||||
pub mod mpsc_queue;
|
||||
|
||||
/// A lock-free single-producer, single consumer queue.
|
||||
pub mod spsc_queue;
|
||||
|
||||
/// A lock-free multi-producer, multi-consumer bounded queue.
|
||||
mod mpmc_bounded_queue;
|
||||
|
||||
/// A parallel work-stealing deque
|
||||
pub mod deque;
|
||||
|
||||
/// A parallel data structure for tracking sleeping schedulers.
|
||||
pub mod sleeper_list;
|
||||
|
||||
/// Stack segments and caching.
|
||||
pub mod stack;
|
||||
|
||||
/// CPU context swapping.
|
||||
mod context;
|
||||
|
||||
/// Bindings to system threading libraries.
|
||||
pub mod thread;
|
||||
|
||||
|
@ -157,16 +117,6 @@ pub mod logging;
|
|||
/// Crate map
|
||||
pub mod crate_map;
|
||||
|
||||
/// Tools for testing the runtime
|
||||
pub mod test;
|
||||
|
||||
/// Reference counting
|
||||
pub mod rc;
|
||||
|
||||
/// A simple single-threaded channel type for passing buffered data between
|
||||
/// scheduler and task context
|
||||
pub mod tube;
|
||||
|
||||
/// The runtime needs to be able to put a pointer into thread-local storage.
|
||||
mod local_ptr;
|
||||
|
||||
|
@ -185,41 +135,33 @@ pub mod args;
|
|||
// Support for dynamic borrowck
|
||||
pub mod borrowck;
|
||||
|
||||
/// Set up a default runtime configuration, given compiler-supplied arguments.
|
||||
///
|
||||
/// This is invoked by the `start` _language item_ (unstable::lang) to
|
||||
/// run a Rust executable.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `argc` & `argv` - The argument vector. On Unix this information is used
|
||||
/// by os::args.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The return value is used as the process return code. 0 on success, 101 on error.
|
||||
pub fn start(argc: int, argv: **u8, main: proc()) -> int {
|
||||
/// The default error code of the rust runtime if the main task fails instead
|
||||
/// of exiting cleanly.
|
||||
pub static DEFAULT_ERROR_CODE: int = 101;
|
||||
|
||||
init(argc, argv);
|
||||
let exit_code = run(main);
|
||||
// unsafe is ok b/c we're sure that the runtime is gone
|
||||
unsafe { cleanup(); }
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
/// Like `start` but creates an additional scheduler on the current thread,
|
||||
/// which in most cases will be the 'main' thread, and pins the main task to it.
|
||||
/// The interface to the current runtime.
|
||||
///
|
||||
/// This is appropriate for running code that must execute on the main thread,
|
||||
/// such as the platform event loop and GUI.
|
||||
pub fn start_on_main_thread(argc: int, argv: **u8, main: proc()) -> int {
|
||||
init(argc, argv);
|
||||
let exit_code = run_on_main_thread(main);
|
||||
// unsafe is ok b/c we're sure that the runtime is gone
|
||||
unsafe { cleanup(); }
|
||||
/// This trait is used as the abstraction between 1:1 and M:N scheduling. The
|
||||
/// two independent crates, libnative and libgreen, both have objects which
|
||||
/// implement this trait. The goal of this trait is to encompass all the
|
||||
/// fundamental differences in functionality between the 1:1 and M:N runtime
|
||||
/// modes.
|
||||
pub trait Runtime {
|
||||
// Necessary scheduling functions, used for channels and blocking I/O
|
||||
// (sometimes).
|
||||
fn yield_now(~self, cur_task: ~Task);
|
||||
fn maybe_yield(~self, cur_task: ~Task);
|
||||
fn deschedule(~self, times: uint, cur_task: ~Task,
|
||||
f: |BlockedTask| -> Result<(), BlockedTask>);
|
||||
fn reawaken(~self, to_wake: ~Task, can_resched: bool);
|
||||
|
||||
return exit_code;
|
||||
// Miscellaneous calls which are very different depending on what context
|
||||
// you're in.
|
||||
fn spawn_sibling(~self, cur_task: ~Task, opts: TaskOpts, f: proc());
|
||||
fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>>;
|
||||
|
||||
// XXX: This is a serious code smell and this should not exist at all.
|
||||
fn wrap(~self) -> ~Any;
|
||||
}
|
||||
|
||||
/// One-time runtime initialization.
|
||||
|
@ -234,6 +176,7 @@ pub fn init(argc: int, argv: **u8) {
|
|||
args::init(argc, argv);
|
||||
env::init();
|
||||
logging::init();
|
||||
local_ptr::init();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,239 +193,3 @@ pub unsafe fn cleanup() {
|
|||
args::cleanup();
|
||||
local_ptr::cleanup();
|
||||
}
|
||||
|
||||
/// Execute the main function in a scheduler.
|
||||
///
|
||||
/// Configures the runtime according to the environment, by default
|
||||
/// using a task scheduler with the same number of threads as cores.
|
||||
/// Returns a process exit code.
|
||||
pub fn run(main: proc()) -> int {
|
||||
run_(main, false)
|
||||
}
|
||||
|
||||
pub fn run_on_main_thread(main: proc()) -> int {
|
||||
run_(main, true)
|
||||
}
|
||||
|
||||
fn run_(main: proc(), use_main_sched: bool) -> int {
|
||||
static DEFAULT_ERROR_CODE: int = 101;
|
||||
|
||||
let nscheds = util::default_sched_threads();
|
||||
|
||||
let mut main = Some(main);
|
||||
|
||||
// The shared list of sleeping schedulers.
|
||||
let sleepers = SleeperList::new();
|
||||
|
||||
// Create a work queue for each scheduler, ntimes. Create an extra
|
||||
// for the main thread if that flag is set. We won't steal from it.
|
||||
let mut pool = deque::BufferPool::new();
|
||||
let arr = vec::from_fn(nscheds, |_| pool.deque());
|
||||
let (workers, stealers) = vec::unzip(arr.move_iter());
|
||||
|
||||
// The schedulers.
|
||||
let mut scheds = ~[];
|
||||
// Handles to the schedulers. When the main task ends these will be
|
||||
// sent the Shutdown message to terminate the schedulers.
|
||||
let mut handles = ~[];
|
||||
|
||||
for worker in workers.move_iter() {
|
||||
rtdebug!("inserting a regular scheduler");
|
||||
|
||||
// Every scheduler is driven by an I/O event loop.
|
||||
let loop_ = new_event_loop();
|
||||
let mut sched = ~Scheduler::new(loop_,
|
||||
worker,
|
||||
stealers.clone(),
|
||||
sleepers.clone());
|
||||
let handle = sched.make_handle();
|
||||
|
||||
scheds.push(sched);
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// If we need a main-thread task then create a main thread scheduler
|
||||
// that will reject any task that isn't pinned to it
|
||||
let main_sched = if use_main_sched {
|
||||
|
||||
// Create a friend handle.
|
||||
let mut friend_sched = scheds.pop();
|
||||
let friend_handle = friend_sched.make_handle();
|
||||
scheds.push(friend_sched);
|
||||
|
||||
// This scheduler needs a queue that isn't part of the stealee
|
||||
// set.
|
||||
let (worker, _) = pool.deque();
|
||||
|
||||
let main_loop = new_event_loop();
|
||||
let mut main_sched = ~Scheduler::new_special(main_loop,
|
||||
worker,
|
||||
stealers.clone(),
|
||||
sleepers.clone(),
|
||||
false,
|
||||
Some(friend_handle));
|
||||
let mut main_handle = main_sched.make_handle();
|
||||
// Allow the scheduler to exit when the main task exits.
|
||||
// Note: sending the shutdown message also prevents the scheduler
|
||||
// from pushing itself to the sleeper list, which is used for
|
||||
// waking up schedulers for work stealing; since this is a
|
||||
// non-work-stealing scheduler it should not be adding itself
|
||||
// to the list.
|
||||
main_handle.send(Shutdown);
|
||||
Some(main_sched)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create a shared cell for transmitting the process exit
|
||||
// code from the main task to this function.
|
||||
let exit_code = UnsafeArc::new(AtomicInt::new(0));
|
||||
let exit_code_clone = exit_code.clone();
|
||||
|
||||
// Used to sanity check that the runtime only exits once
|
||||
let exited_already = UnsafeArc::new(AtomicBool::new(false));
|
||||
|
||||
// When the main task exits, after all the tasks in the main
|
||||
// task tree, shut down the schedulers and set the exit code.
|
||||
let handles = handles;
|
||||
let on_exit: proc(TaskResult) = proc(exit_success) {
|
||||
unsafe {
|
||||
assert!(!(*exited_already.get()).swap(true, SeqCst),
|
||||
"the runtime already exited");
|
||||
}
|
||||
|
||||
let mut handles = handles;
|
||||
for handle in handles.mut_iter() {
|
||||
handle.send(Shutdown);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let exit_code = if exit_success.is_ok() {
|
||||
use rt::util;
|
||||
|
||||
// If we're exiting successfully, then return the global
|
||||
// exit status, which can be set programmatically.
|
||||
util::get_exit_status()
|
||||
} else {
|
||||
DEFAULT_ERROR_CODE
|
||||
};
|
||||
(*exit_code_clone.get()).store(exit_code, SeqCst);
|
||||
}
|
||||
};
|
||||
|
||||
let mut threads = ~[];
|
||||
let mut on_exit = Some(on_exit);
|
||||
|
||||
if !use_main_sched {
|
||||
|
||||
// In the case where we do not use a main_thread scheduler we
|
||||
// run the main task in one of our threads.
|
||||
|
||||
let mut main_task = ~Task::new_root(&mut scheds[0].stack_pool,
|
||||
None,
|
||||
::util::replace(&mut main,
|
||||
None).unwrap());
|
||||
main_task.name = Some(SendStrStatic("<main>"));
|
||||
main_task.death.on_exit = ::util::replace(&mut on_exit, None);
|
||||
|
||||
let sched = scheds.pop();
|
||||
let main_task = main_task;
|
||||
let thread = do Thread::start {
|
||||
sched.bootstrap(main_task);
|
||||
};
|
||||
threads.push(thread);
|
||||
}
|
||||
|
||||
// Run each remaining scheduler in a thread.
|
||||
for sched in scheds.move_rev_iter() {
|
||||
rtdebug!("creating regular schedulers");
|
||||
let thread = do Thread::start {
|
||||
let mut sched = sched;
|
||||
let bootstrap_task = ~do Task::new_root(&mut sched.stack_pool, None) || {
|
||||
rtdebug!("boostraping a non-primary scheduler");
|
||||
};
|
||||
sched.bootstrap(bootstrap_task);
|
||||
};
|
||||
threads.push(thread);
|
||||
}
|
||||
|
||||
// If we do have a main thread scheduler, run it now.
|
||||
|
||||
if use_main_sched {
|
||||
rtdebug!("about to create the main scheduler task");
|
||||
|
||||
let mut main_sched = main_sched.unwrap();
|
||||
|
||||
let home = Sched(main_sched.make_handle());
|
||||
let mut main_task = ~Task::new_root_homed(&mut main_sched.stack_pool,
|
||||
None,
|
||||
home,
|
||||
::util::replace(&mut main,
|
||||
None).
|
||||
unwrap());
|
||||
main_task.name = Some(SendStrStatic("<main>"));
|
||||
main_task.death.on_exit = ::util::replace(&mut on_exit, None);
|
||||
rtdebug!("bootstrapping main_task");
|
||||
|
||||
main_sched.bootstrap(main_task);
|
||||
}
|
||||
|
||||
rtdebug!("waiting for threads");
|
||||
|
||||
// Wait for schedulers
|
||||
for thread in threads.move_iter() {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
// Return the exit code
|
||||
unsafe {
|
||||
(*exit_code.get()).load(SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_sched_context() -> bool {
|
||||
unsafe {
|
||||
let task_ptr: Option<*mut Task> = Local::try_unsafe_borrow();
|
||||
match task_ptr {
|
||||
Some(task) => {
|
||||
match (*task).task_type {
|
||||
SchedTask => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_green_task_context() -> bool {
|
||||
unsafe {
|
||||
let task: Option<*mut Task> = Local::try_unsafe_borrow();
|
||||
match task {
|
||||
Some(task) => {
|
||||
match (*task).task_type {
|
||||
GreenTask(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
None => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_event_loop() -> ~rtio::EventLoop {
|
||||
match crate_map::get_crate_map() {
|
||||
None => {}
|
||||
Some(map) => {
|
||||
match map.event_loop_factory {
|
||||
None => {}
|
||||
Some(factory) => return factory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the crate map didn't specify a factory to create an event loop, then
|
||||
// instead just use a basic event loop missing all I/O services to at least
|
||||
// get the scheduler running.
|
||||
return basic::event_loop();
|
||||
}
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! An owned, task-local, reference counted type
|
||||
//!
|
||||
//! # Safety note
|
||||
//!
|
||||
//! XXX There is currently no type-system mechanism for enforcing that
|
||||
//! reference counted types are both allocated on the exchange heap
|
||||
//! and also non-sendable
|
||||
//!
|
||||
//! This doesn't prevent borrowing multiple aliasable mutable pointers
|
||||
|
||||
use ops::Drop;
|
||||
use clone::Clone;
|
||||
use libc::c_void;
|
||||
use cast;
|
||||
|
||||
pub struct RC<T> {
|
||||
priv p: *c_void // ~(uint, T)
|
||||
}
|
||||
|
||||
impl<T> RC<T> {
|
||||
pub fn new(val: T) -> RC<T> {
|
||||
unsafe {
|
||||
let v = ~(1, val);
|
||||
let p: *c_void = cast::transmute(v);
|
||||
RC { p: p }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut_state(&mut self) -> *mut (uint, T) {
|
||||
unsafe {
|
||||
let p: &mut ~(uint, T) = cast::transmute(&mut self.p);
|
||||
let p: *mut (uint, T) = &mut **p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state(&self) -> *(uint, T) {
|
||||
unsafe {
|
||||
let p: &~(uint, T) = cast::transmute(&self.p);
|
||||
let p: *(uint, T) = &**p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsafe_borrow_mut(&mut self) -> *mut T {
|
||||
unsafe {
|
||||
match *self.get_mut_state() {
|
||||
(_, ref mut p) => {
|
||||
let p: *mut T = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refcount(&self) -> uint {
|
||||
unsafe {
|
||||
match *self.get_state() {
|
||||
(count, _) => count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe_destructor]
|
||||
impl<T> Drop for RC<T> {
|
||||
fn drop(&mut self) {
|
||||
assert!(self.refcount() > 0);
|
||||
|
||||
unsafe {
|
||||
match *self.get_mut_state() {
|
||||
(ref mut count, _) => {
|
||||
*count = *count - 1
|
||||
}
|
||||
}
|
||||
|
||||
if self.refcount() == 0 {
|
||||
let _: ~(uint, T) = cast::transmute(self.p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for RC<T> {
|
||||
fn clone(&self) -> RC<T> {
|
||||
unsafe {
|
||||
// XXX: Mutable clone
|
||||
let this: &mut RC<T> = cast::transmute_mut(self);
|
||||
|
||||
match *this.get_mut_state() {
|
||||
(ref mut count, _) => {
|
||||
*count = *count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RC { p: self.p }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::RC;
|
||||
|
||||
#[test]
|
||||
fn smoke_test() {
|
||||
unsafe {
|
||||
let mut v1 = RC::new(100);
|
||||
assert!(*v1.unsafe_borrow_mut() == 100);
|
||||
assert!(v1.refcount() == 1);
|
||||
|
||||
let mut v2 = v1.clone();
|
||||
assert!(*v2.unsafe_borrow_mut() == 100);
|
||||
assert!(v2.refcount() == 2);
|
||||
|
||||
*v2.unsafe_borrow_mut() = 200;
|
||||
assert!(*v2.unsafe_borrow_mut() == 200);
|
||||
assert!(*v1.unsafe_borrow_mut() == 200);
|
||||
|
||||
let v3 = v2.clone();
|
||||
assert!(v3.refcount() == 3);
|
||||
{
|
||||
let _v1 = v1;
|
||||
let _v2 = v2;
|
||||
}
|
||||
assert!(v3.refcount() == 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,14 +14,15 @@ use comm::{SharedChan, Port};
|
|||
use libc::c_int;
|
||||
use libc;
|
||||
use ops::Drop;
|
||||
use option::*;
|
||||
use option::{Option, Some, None};
|
||||
use path::Path;
|
||||
use result::*;
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::task::Task;
|
||||
use rt::local::Local;
|
||||
|
||||
use ai = io::net::addrinfo;
|
||||
use io;
|
||||
use io::IoError;
|
||||
use io::native::NATIVE_IO_FACTORY;
|
||||
use io::native;
|
||||
use io::net::ip::{IpAddr, SocketAddr};
|
||||
use io::process::{ProcessConfig, ProcessExit};
|
||||
use io::signal::Signum;
|
||||
|
@ -93,34 +94,50 @@ impl<'a> Drop for LocalIo<'a> {
|
|||
impl<'a> LocalIo<'a> {
|
||||
/// Returns the local I/O: either the local scheduler's I/O services or
|
||||
/// the native I/O services.
|
||||
pub fn borrow() -> LocalIo {
|
||||
use rt::sched::Scheduler;
|
||||
use rt::local::Local;
|
||||
pub fn borrow() -> Option<LocalIo> {
|
||||
// FIXME(#11053): bad
|
||||
//
|
||||
// This is currently very unsafely implemented. We don't actually
|
||||
// *take* the local I/O so there's a very real possibility that we
|
||||
// can have two borrows at once. Currently there is not a clear way
|
||||
// to actually borrow the local I/O factory safely because even if
|
||||
// ownership were transferred down to the functions that the I/O
|
||||
// factory implements it's just too much of a pain to know when to
|
||||
// relinquish ownership back into the local task (but that would be
|
||||
// the safe way of implementing this function).
|
||||
//
|
||||
// In order to get around this, we just transmute a copy out of the task
|
||||
// in order to have what is likely a static lifetime (bad).
|
||||
let mut t: ~Task = Local::take();
|
||||
let ret = t.local_io().map(|t| {
|
||||
unsafe { cast::transmute_copy(&t) }
|
||||
});
|
||||
Local::put(t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// First, attempt to use the local scheduler's I/O services
|
||||
let sched: Option<*mut Scheduler> = Local::try_unsafe_borrow();
|
||||
match sched {
|
||||
Some(sched) => {
|
||||
match (*sched).event_loop.io() {
|
||||
Some(factory) => {
|
||||
return LocalIo {
|
||||
factory: factory,
|
||||
pub fn maybe_raise<T>(f: |io: &mut IoFactory| -> Result<T, IoError>)
|
||||
-> Option<T>
|
||||
{
|
||||
match LocalIo::borrow() {
|
||||
None => {
|
||||
io::io_error::cond.raise(io::standard_error(io::IoUnavailable));
|
||||
None
|
||||
}
|
||||
Some(mut io) => {
|
||||
match f(io.get()) {
|
||||
Ok(t) => Some(t),
|
||||
Err(ioerr) => {
|
||||
io::io_error::cond.raise(ioerr);
|
||||
None
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
// If we don't have a scheduler or the scheduler doesn't have I/O
|
||||
// services, then fall back to the native I/O services.
|
||||
let native_io: &'static mut native::IoFactory =
|
||||
&mut NATIVE_IO_FACTORY;
|
||||
LocalIo {
|
||||
factory: native_io as &mut IoFactory:'static
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<'a>(io: &'a mut IoFactory) -> LocalIo<'a> {
|
||||
LocalIo { factory: io }
|
||||
}
|
||||
|
||||
/// Returns the underlying I/O factory as a trait reference.
|
||||
|
|
|
@ -13,29 +13,41 @@
|
|||
//! local storage, and logging. Even a 'freestanding' Rust would likely want
|
||||
//! to implement this.
|
||||
|
||||
use super::local_heap::LocalHeap;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use any::AnyOwnExt;
|
||||
use borrow;
|
||||
use cast;
|
||||
use cleanup;
|
||||
use io::Writer;
|
||||
use libc::{c_char, size_t};
|
||||
use iter::{Iterator, Take};
|
||||
use local_data;
|
||||
use ops::Drop;
|
||||
use option::{Option, Some, None};
|
||||
use prelude::drop;
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::Runtime;
|
||||
use rt::borrowck::BorrowRecord;
|
||||
use rt::borrowck;
|
||||
use rt::context::Context;
|
||||
use rt::env;
|
||||
use rt::kill::Death;
|
||||
use rt::local::Local;
|
||||
use rt::local_heap::LocalHeap;
|
||||
use rt::logging::StdErrLogger;
|
||||
use rt::sched::{Scheduler, SchedHandle};
|
||||
use rt::stack::{StackSegment, StackPool};
|
||||
use rt::rtio::LocalIo;
|
||||
use rt::unwind::Unwinder;
|
||||
use send_str::SendStr;
|
||||
use sync::arc::UnsafeArc;
|
||||
use sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT};
|
||||
use task::{TaskResult, TaskOpts};
|
||||
use unstable::finally::Finally;
|
||||
use unstable::mutex::Mutex;
|
||||
use unstable::mutex::{Mutex, MUTEX_INIT};
|
||||
|
||||
#[cfg(stage0)]
|
||||
pub use rt::unwind::begin_unwind;
|
||||
|
||||
// These two statics are used as bookeeping to keep track of the rust runtime's
|
||||
// count of threads. In 1:1 contexts, this is used to know when to return from
|
||||
// the main function, and in M:N contexts this is used to know when to shut down
|
||||
// the pool of schedulers.
|
||||
static mut TASK_COUNT: AtomicUint = INIT_ATOMIC_UINT;
|
||||
static mut TASK_LOCK: Mutex = MUTEX_INIT;
|
||||
|
||||
// The Task struct represents all state associated with a rust
|
||||
// task. There are at this point two primary "subtypes" of task,
|
||||
|
@ -45,201 +57,90 @@ use unstable::mutex::Mutex;
|
|||
|
||||
pub struct Task {
|
||||
heap: LocalHeap,
|
||||
priv gc: GarbageCollector,
|
||||
gc: GarbageCollector,
|
||||
storage: LocalStorage,
|
||||
logger: Option<StdErrLogger>,
|
||||
unwinder: Unwinder,
|
||||
death: Death,
|
||||
destroyed: bool,
|
||||
name: Option<SendStr>,
|
||||
coroutine: Option<Coroutine>,
|
||||
sched: Option<~Scheduler>,
|
||||
task_type: TaskType,
|
||||
// Dynamic borrowck debugging info
|
||||
borrow_list: Option<~[BorrowRecord]>,
|
||||
|
||||
logger: Option<StdErrLogger>,
|
||||
stdout_handle: Option<~Writer>,
|
||||
|
||||
// See the comments in the scheduler about why this is necessary
|
||||
nasty_deschedule_lock: Mutex,
|
||||
}
|
||||
|
||||
pub enum TaskType {
|
||||
GreenTask(Option<SchedHome>),
|
||||
SchedTask
|
||||
}
|
||||
|
||||
/// A coroutine is nothing more than a (register context, stack) pair.
|
||||
pub struct Coroutine {
|
||||
/// The segment of stack on which the task is currently running or
|
||||
/// if the task is blocked, on which the task will resume
|
||||
/// execution.
|
||||
///
|
||||
/// Servo needs this to be public in order to tell SpiderMonkey
|
||||
/// about the stack bounds.
|
||||
current_stack_segment: StackSegment,
|
||||
/// Always valid if the task is alive and not running.
|
||||
saved_context: Context
|
||||
}
|
||||
|
||||
/// Some tasks have a dedicated home scheduler that they must run on.
|
||||
pub enum SchedHome {
|
||||
AnySched,
|
||||
Sched(SchedHandle)
|
||||
priv imp: Option<~Runtime>,
|
||||
}
|
||||
|
||||
pub struct GarbageCollector;
|
||||
pub struct LocalStorage(Option<local_data::Map>);
|
||||
|
||||
/// A handle to a blocked task. Usually this means having the ~Task pointer by
|
||||
/// ownership, but if the task is killable, a killer can steal it at any time.
|
||||
pub enum BlockedTask {
|
||||
Owned(~Task),
|
||||
Shared(UnsafeArc<AtomicUint>),
|
||||
}
|
||||
|
||||
/// Per-task state related to task death, killing, failure, etc.
|
||||
pub struct Death {
|
||||
// Action to be done with the exit code. If set, also makes the task wait
|
||||
// until all its watched children exit before collecting the status.
|
||||
on_exit: Option<proc(TaskResult)>,
|
||||
}
|
||||
|
||||
pub struct BlockedTaskIterator {
|
||||
priv inner: UnsafeArc<AtomicUint>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
|
||||
// A helper to build a new task using the dynamically found
|
||||
// scheduler and task. Only works in GreenTask context.
|
||||
pub fn build_homed_child(stack_size: Option<uint>,
|
||||
f: proc(),
|
||||
home: SchedHome)
|
||||
-> ~Task {
|
||||
let mut running_task = Local::borrow(None::<Task>);
|
||||
let mut sched = running_task.get().sched.take_unwrap();
|
||||
let new_task = ~running_task.get()
|
||||
.new_child_homed(&mut sched.stack_pool,
|
||||
stack_size,
|
||||
home,
|
||||
f);
|
||||
running_task.get().sched = Some(sched);
|
||||
new_task
|
||||
}
|
||||
|
||||
pub fn build_child(stack_size: Option<uint>, f: proc()) -> ~Task {
|
||||
Task::build_homed_child(stack_size, f, AnySched)
|
||||
}
|
||||
|
||||
pub fn build_homed_root(stack_size: Option<uint>,
|
||||
f: proc(),
|
||||
home: SchedHome)
|
||||
-> ~Task {
|
||||
let mut running_task = Local::borrow(None::<Task>);
|
||||
let mut sched = running_task.get().sched.take_unwrap();
|
||||
let new_task = ~Task::new_root_homed(&mut sched.stack_pool,
|
||||
stack_size,
|
||||
home,
|
||||
f);
|
||||
running_task.get().sched = Some(sched);
|
||||
new_task
|
||||
}
|
||||
|
||||
pub fn build_root(stack_size: Option<uint>, f: proc()) -> ~Task {
|
||||
Task::build_homed_root(stack_size, f, AnySched)
|
||||
}
|
||||
|
||||
pub fn new_sched_task() -> Task {
|
||||
pub fn new() -> Task {
|
||||
Task {
|
||||
heap: LocalHeap::new(),
|
||||
gc: GarbageCollector,
|
||||
storage: LocalStorage(None),
|
||||
logger: None,
|
||||
unwinder: Unwinder { unwinding: false, cause: None },
|
||||
death: Death::new(),
|
||||
destroyed: false,
|
||||
coroutine: Some(Coroutine::empty()),
|
||||
name: None,
|
||||
sched: None,
|
||||
task_type: SchedTask,
|
||||
borrow_list: None,
|
||||
stdout_handle: None,
|
||||
nasty_deschedule_lock: unsafe { Mutex::new() },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_root(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
start: proc()) -> Task {
|
||||
Task::new_root_homed(stack_pool, stack_size, AnySched, start)
|
||||
}
|
||||
|
||||
pub fn new_child(&mut self,
|
||||
stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
start: proc()) -> Task {
|
||||
self.new_child_homed(stack_pool, stack_size, AnySched, start)
|
||||
}
|
||||
|
||||
pub fn new_root_homed(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
home: SchedHome,
|
||||
start: proc()) -> Task {
|
||||
Task {
|
||||
heap: LocalHeap::new(),
|
||||
gc: GarbageCollector,
|
||||
storage: LocalStorage(None),
|
||||
logger: None,
|
||||
unwinder: Unwinder { unwinding: false, cause: None },
|
||||
unwinder: Unwinder::new(),
|
||||
death: Death::new(),
|
||||
destroyed: false,
|
||||
name: None,
|
||||
coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
|
||||
sched: None,
|
||||
task_type: GreenTask(Some(home)),
|
||||
borrow_list: None,
|
||||
stdout_handle: None,
|
||||
nasty_deschedule_lock: unsafe { Mutex::new() },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_child_homed(&mut self,
|
||||
stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
home: SchedHome,
|
||||
start: proc()) -> Task {
|
||||
Task {
|
||||
heap: LocalHeap::new(),
|
||||
gc: GarbageCollector,
|
||||
storage: LocalStorage(None),
|
||||
logger: None,
|
||||
unwinder: Unwinder { unwinding: false, cause: None },
|
||||
death: Death::new(),
|
||||
destroyed: false,
|
||||
name: None,
|
||||
coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
|
||||
sched: None,
|
||||
task_type: GreenTask(Some(home)),
|
||||
borrow_list: None,
|
||||
stdout_handle: None,
|
||||
nasty_deschedule_lock: unsafe { Mutex::new() },
|
||||
imp: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn give_home(&mut self, new_home: SchedHome) {
|
||||
match self.task_type {
|
||||
GreenTask(ref mut home) => {
|
||||
*home = Some(new_home);
|
||||
}
|
||||
SchedTask => {
|
||||
rtabort!("type error: used SchedTask as GreenTask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_unwrap_home(&mut self) -> SchedHome {
|
||||
match self.task_type {
|
||||
GreenTask(ref mut home) => {
|
||||
let out = home.take_unwrap();
|
||||
return out;
|
||||
}
|
||||
SchedTask => {
|
||||
rtabort!("type error: used SchedTask as GreenTask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, f: ||) {
|
||||
rtdebug!("run called on task: {}", borrow::to_uint(self));
|
||||
/// Executes the given closure as if it's running inside this task. The task
|
||||
/// is consumed upon entry, and the destroyed task is returned from this
|
||||
/// function in order for the caller to free. This function is guaranteed to
|
||||
/// not unwind because the closure specified is run inside of a `rust_try`
|
||||
/// block. (this is the only try/catch block in the world).
|
||||
///
|
||||
/// This function is *not* meant to be abused as a "try/catch" block. This
|
||||
/// is meant to be used at the absolute boundaries of a task's lifetime, and
|
||||
/// only for that purpose.
|
||||
pub fn run(~self, f: ||) -> ~Task {
|
||||
// Need to put ourselves into TLS, but also need access to the unwinder.
|
||||
// Unsafely get a handle to the task so we can continue to use it after
|
||||
// putting it in tls (so we can invoke the unwinder).
|
||||
let handle: *mut Task = unsafe {
|
||||
*cast::transmute::<&~Task, &*mut Task>(&self)
|
||||
};
|
||||
Local::put(self);
|
||||
unsafe { TASK_COUNT.fetch_add(1, SeqCst); }
|
||||
|
||||
// The only try/catch block in the world. Attempt to run the task's
|
||||
// client-specified code and catch any failures.
|
||||
self.unwinder.try(|| {
|
||||
let try_block = || {
|
||||
|
||||
// Run the task main function, then do some cleanup.
|
||||
f.finally(|| {
|
||||
fn flush(w: Option<~Writer>) {
|
||||
match w {
|
||||
Some(mut w) => { w.flush(); }
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// First, destroy task-local storage. This may run user dtors.
|
||||
//
|
||||
|
@ -260,7 +161,10 @@ impl Task {
|
|||
// TLS, or possibly some destructors for those objects being
|
||||
// annihilated invoke TLS. Sadly these two operations seemed to
|
||||
// be intertwined, and miraculously work for now...
|
||||
self.storage.take();
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
let storage = task.get().storage.take();
|
||||
drop(task);
|
||||
drop(storage);
|
||||
|
||||
// Destroy remaining boxes. Also may run user dtors.
|
||||
unsafe { cleanup::annihilate(); }
|
||||
|
@ -268,77 +172,141 @@ impl Task {
|
|||
// Finally flush and destroy any output handles which the task
|
||||
// owns. There are no boxes here, and no user destructors should
|
||||
// run after this any more.
|
||||
match self.stdout_handle.take() {
|
||||
Some(handle) => {
|
||||
let mut handle = handle;
|
||||
handle.flush();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.logger.take();
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
let stdout = task.get().stdout_handle.take();
|
||||
let logger = task.get().logger.take();
|
||||
drop(task);
|
||||
|
||||
flush(stdout);
|
||||
drop(logger);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
unsafe { (*handle).unwinder.try(try_block); }
|
||||
|
||||
// Cleanup the dynamic borrowck debugging info
|
||||
borrowck::clear_task_borrow_list();
|
||||
|
||||
self.death.collect_failure(self.unwinder.result());
|
||||
self.destroyed = true;
|
||||
// Here we must unsafely borrow the task in order to not remove it from
|
||||
// TLS. When collecting failure, we may attempt to send on a channel (or
|
||||
// just run aribitrary code), so we must be sure to still have a local
|
||||
// task in TLS.
|
||||
unsafe {
|
||||
let me: *mut Task = Local::unsafe_borrow();
|
||||
(*me).death.collect_failure((*me).unwinder.result());
|
||||
|
||||
// see comments on these statics for why they're used
|
||||
if TASK_COUNT.fetch_sub(1, SeqCst) == 1 {
|
||||
TASK_LOCK.lock();
|
||||
TASK_LOCK.signal();
|
||||
TASK_LOCK.unlock();
|
||||
}
|
||||
}
|
||||
let mut me: ~Task = Local::take();
|
||||
me.destroyed = true;
|
||||
return me;
|
||||
}
|
||||
|
||||
// New utility functions for homes.
|
||||
/// Inserts a runtime object into this task, transferring ownership to the
|
||||
/// task. It is illegal to replace a previous runtime object in this task
|
||||
/// with this argument.
|
||||
pub fn put_runtime(&mut self, ops: ~Runtime) {
|
||||
assert!(self.imp.is_none());
|
||||
self.imp = Some(ops);
|
||||
}
|
||||
|
||||
pub fn is_home_no_tls(&self, sched: &~Scheduler) -> bool {
|
||||
match self.task_type {
|
||||
GreenTask(Some(AnySched)) => { false }
|
||||
GreenTask(Some(Sched(SchedHandle { sched_id: ref id, .. }))) => {
|
||||
*id == sched.sched_id()
|
||||
/// Attempts to extract the runtime as a specific type. If the runtime does
|
||||
/// not have the provided type, then the runtime is not removed. If the
|
||||
/// runtime does have the specified type, then it is removed and returned
|
||||
/// (transfer of ownership).
|
||||
///
|
||||
/// It is recommended to only use this method when *absolutely necessary*.
|
||||
/// This function may not be available in the future.
|
||||
pub fn maybe_take_runtime<T: 'static>(&mut self) -> Option<~T> {
|
||||
// This is a terrible, terrible function. The general idea here is to
|
||||
// take the runtime, cast it to ~Any, check if it has the right type,
|
||||
// and then re-cast it back if necessary. The method of doing this is
|
||||
// pretty sketchy and involves shuffling vtables of trait objects
|
||||
// around, but it gets the job done.
|
||||
//
|
||||
// XXX: This function is a serious code smell and should be avoided at
|
||||
// all costs. I have yet to think of a method to avoid this
|
||||
// function, and I would be saddened if more usage of the function
|
||||
// crops up.
|
||||
unsafe {
|
||||
let imp = self.imp.take_unwrap();
|
||||
let &(vtable, _): &(uint, uint) = cast::transmute(&imp);
|
||||
match imp.wrap().move::<T>() {
|
||||
Ok(t) => Some(t),
|
||||
Err(t) => {
|
||||
let (_, obj): (uint, uint) = cast::transmute(t);
|
||||
let obj: ~Runtime = cast::transmute((vtable, obj));
|
||||
self.put_runtime(obj);
|
||||
None
|
||||
}
|
||||
GreenTask(None) => {
|
||||
rtabort!("task without home");
|
||||
}
|
||||
SchedTask => {
|
||||
// Awe yea
|
||||
rtabort!("type error: expected: GreenTask, found: SchedTask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn homed(&self) -> bool {
|
||||
match self.task_type {
|
||||
GreenTask(Some(AnySched)) => { false }
|
||||
GreenTask(Some(Sched(SchedHandle { .. }))) => { true }
|
||||
GreenTask(None) => {
|
||||
rtabort!("task without home");
|
||||
}
|
||||
SchedTask => {
|
||||
rtabort!("type error: expected: GreenTask, found: SchedTask");
|
||||
}
|
||||
}
|
||||
/// Spawns a sibling to this task. The newly spawned task is configured with
|
||||
/// the `opts` structure and will run `f` as the body of its code.
|
||||
pub fn spawn_sibling(mut ~self, opts: TaskOpts, f: proc()) {
|
||||
let ops = self.imp.take_unwrap();
|
||||
ops.spawn_sibling(self, opts, f)
|
||||
}
|
||||
|
||||
// Grab both the scheduler and the task from TLS and check if the
|
||||
// task is executing on an appropriate scheduler.
|
||||
pub fn on_appropriate_sched() -> bool {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
let sched_id = task.get().sched.get_ref().sched_id();
|
||||
let sched_run_anything = task.get().sched.get_ref().run_anything;
|
||||
match task.get().task_type {
|
||||
GreenTask(Some(AnySched)) => {
|
||||
rtdebug!("anysched task in sched check ****");
|
||||
sched_run_anything
|
||||
/// Deschedules the current task, invoking `f` `amt` times. It is not
|
||||
/// recommended to use this function directly, but rather communication
|
||||
/// primitives in `std::comm` should be used.
|
||||
pub fn deschedule(mut ~self, amt: uint,
|
||||
f: |BlockedTask| -> Result<(), BlockedTask>) {
|
||||
let ops = self.imp.take_unwrap();
|
||||
ops.deschedule(amt, self, f)
|
||||
}
|
||||
GreenTask(Some(Sched(SchedHandle { sched_id: ref id, ..}))) => {
|
||||
rtdebug!("homed task in sched check ****");
|
||||
*id == sched_id
|
||||
|
||||
/// Wakes up a previously blocked task, optionally specifiying whether the
|
||||
/// current task can accept a change in scheduling. This function can only
|
||||
/// be called on tasks that were previously blocked in `deschedule`.
|
||||
pub fn reawaken(mut ~self, can_resched: bool) {
|
||||
let ops = self.imp.take_unwrap();
|
||||
ops.reawaken(self, can_resched);
|
||||
}
|
||||
GreenTask(None) => {
|
||||
rtabort!("task without home");
|
||||
|
||||
/// Yields control of this task to another task. This function will
|
||||
/// eventually return, but possibly not immediately. This is used as an
|
||||
/// opportunity to allow other tasks a chance to run.
|
||||
pub fn yield_now(mut ~self) {
|
||||
let ops = self.imp.take_unwrap();
|
||||
ops.yield_now(self);
|
||||
}
|
||||
SchedTask => {
|
||||
rtabort!("type error: expected: GreenTask, found: SchedTask");
|
||||
|
||||
/// Similar to `yield_now`, except that this function may immediately return
|
||||
/// without yielding (depending on what the runtime decides to do).
|
||||
pub fn maybe_yield(mut ~self) {
|
||||
let ops = self.imp.take_unwrap();
|
||||
ops.maybe_yield(self);
|
||||
}
|
||||
|
||||
/// Acquires a handle to the I/O factory that this task contains, normally
|
||||
/// stored in the task's runtime. This factory may not always be available,
|
||||
/// which is why the return type is `Option`
|
||||
pub fn local_io<'a>(&'a mut self) -> Option<LocalIo<'a>> {
|
||||
self.imp.get_mut_ref().local_io()
|
||||
}
|
||||
|
||||
/// The main function of all rust executables will by default use this
|
||||
/// function. This function will *block* the OS thread (hence the `unsafe`)
|
||||
/// waiting for all known tasks to complete. Once this function has
|
||||
/// returned, it is guaranteed that no more user-defined code is still
|
||||
/// running.
|
||||
pub unsafe fn wait_for_other_tasks(&mut self) {
|
||||
TASK_COUNT.fetch_sub(1, SeqCst); // don't count ourselves
|
||||
TASK_LOCK.lock();
|
||||
while TASK_COUNT.load(SeqCst) > 0 {
|
||||
TASK_LOCK.wait();
|
||||
}
|
||||
TASK_LOCK.unlock();
|
||||
TASK_COUNT.fetch_add(1, SeqCst); // add ourselves back in
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,276 +314,121 @@ impl Drop for Task {
|
|||
fn drop(&mut self) {
|
||||
rtdebug!("called drop for a task: {}", borrow::to_uint(self));
|
||||
rtassert!(self.destroyed);
|
||||
|
||||
unsafe { self.nasty_deschedule_lock.destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Coroutines represent nothing more than a context and a stack
|
||||
// segment.
|
||||
|
||||
impl Coroutine {
|
||||
|
||||
pub fn new(stack_pool: &mut StackPool,
|
||||
stack_size: Option<uint>,
|
||||
start: proc())
|
||||
-> Coroutine {
|
||||
let stack_size = match stack_size {
|
||||
Some(size) => size,
|
||||
None => env::min_stack()
|
||||
};
|
||||
let start = Coroutine::build_start_wrapper(start);
|
||||
let mut stack = stack_pool.take_segment(stack_size);
|
||||
let initial_context = Context::new(start, &mut stack);
|
||||
Coroutine {
|
||||
current_stack_segment: stack,
|
||||
saved_context: initial_context
|
||||
impl Iterator<BlockedTask> for BlockedTaskIterator {
|
||||
fn next(&mut self) -> Option<BlockedTask> {
|
||||
Some(Shared(self.inner.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Coroutine {
|
||||
Coroutine {
|
||||
current_stack_segment: StackSegment::new(0),
|
||||
saved_context: Context::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_start_wrapper(start: proc()) -> proc() {
|
||||
let wrapper: proc() = proc() {
|
||||
// First code after swap to this new context. Run our
|
||||
// cleanup job.
|
||||
unsafe {
|
||||
|
||||
// Again - might work while safe, or it might not.
|
||||
{
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
sched.get().run_cleanup_job();
|
||||
}
|
||||
|
||||
// To call the run method on a task we need a direct
|
||||
// reference to it. The task is in TLS, so we can
|
||||
// simply unsafe_borrow it to get this reference. We
|
||||
// need to still have the task in TLS though, so we
|
||||
// need to unsafe_borrow.
|
||||
let task: *mut Task = Local::unsafe_borrow();
|
||||
|
||||
let mut start_cell = Some(start);
|
||||
(*task).run(|| {
|
||||
// N.B. Removing `start` from the start wrapper
|
||||
// closure by emptying a cell is critical for
|
||||
// correctness. The ~Task pointer, and in turn the
|
||||
// closure used to initialize the first call
|
||||
// frame, is destroyed in the scheduler context,
|
||||
// not task context. So any captured closures must
|
||||
// not contain user-definable dtors that expect to
|
||||
// be in task context. By moving `start` out of
|
||||
// the closure, all the user code goes our of
|
||||
// scope while the task is still running.
|
||||
let start = start_cell.take_unwrap();
|
||||
start();
|
||||
});
|
||||
}
|
||||
|
||||
// We remove the sched from the Task in TLS right now.
|
||||
let sched: ~Scheduler = Local::take();
|
||||
// ... allowing us to give it away when performing a
|
||||
// scheduling operation.
|
||||
sched.terminate_current_task()
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/// Destroy coroutine and try to reuse stack segment.
|
||||
pub fn recycle(self, stack_pool: &mut StackPool) {
|
||||
impl BlockedTask {
|
||||
/// Returns Some if the task was successfully woken; None if already killed.
|
||||
pub fn wake(self) -> Option<~Task> {
|
||||
match self {
|
||||
Coroutine { current_stack_segment, .. } => {
|
||||
stack_pool.give_segment(current_stack_segment);
|
||||
Owned(task) => Some(task),
|
||||
Shared(arc) => unsafe {
|
||||
match (*arc.get()).swap(0, SeqCst) {
|
||||
0 => None,
|
||||
n => Some(cast::transmute(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This assertion has two flavours because the wake involves an atomic op.
|
||||
// In the faster version, destructors will fail dramatically instead.
|
||||
#[cfg(not(test))] pub fn trash(self) { }
|
||||
#[cfg(test)] pub fn trash(self) { assert!(self.wake().is_none()); }
|
||||
|
||||
/// Create a blocked task, unless the task was already killed.
|
||||
pub fn block(task: ~Task) -> BlockedTask {
|
||||
Owned(task)
|
||||
}
|
||||
|
||||
/// This function is invoked from rust's current __morestack function. Segmented
|
||||
/// stacks are currently not enabled as segmented stacks, but rather one giant
|
||||
/// stack segment. This means that whenever we run out of stack, we want to
|
||||
/// truly consider it to be stack overflow rather than allocating a new stack.
|
||||
#[no_mangle] // - this is called from C code
|
||||
#[no_split_stack] // - it would be sad for this function to trigger __morestack
|
||||
#[doc(hidden)] // - Function must be `pub` to get exported, but it's
|
||||
// irrelevant for documentation purposes.
|
||||
#[cfg(not(test))] // in testing, use the original libstd's version
|
||||
pub extern "C" fn rust_stack_exhausted() {
|
||||
use rt::context;
|
||||
use rt::in_green_task_context;
|
||||
use rt::task::Task;
|
||||
use rt::local::Local;
|
||||
use unstable::intrinsics;
|
||||
|
||||
unsafe {
|
||||
// We're calling this function because the stack just ran out. We need
|
||||
// to call some other rust functions, but if we invoke the functions
|
||||
// right now it'll just trigger this handler being called again. In
|
||||
// order to alleviate this, we move the stack limit to be inside of the
|
||||
// red zone that was allocated for exactly this reason.
|
||||
let limit = context::get_sp_limit();
|
||||
context::record_sp_limit(limit - context::RED_ZONE / 2);
|
||||
|
||||
// This probably isn't the best course of action. Ideally one would want
|
||||
// to unwind the stack here instead of just aborting the entire process.
|
||||
// This is a tricky problem, however. There's a few things which need to
|
||||
// be considered:
|
||||
//
|
||||
// 1. We're here because of a stack overflow, yet unwinding will run
|
||||
// destructors and hence arbitrary code. What if that code overflows
|
||||
// the stack? One possibility is to use the above allocation of an
|
||||
// extra 10k to hope that we don't hit the limit, and if we do then
|
||||
// abort the whole program. Not the best, but kind of hard to deal
|
||||
// with unless we want to switch stacks.
|
||||
//
|
||||
// 2. LLVM will optimize functions based on whether they can unwind or
|
||||
// not. It will flag functions with 'nounwind' if it believes that
|
||||
// the function cannot trigger unwinding, but if we do unwind on
|
||||
// stack overflow then it means that we could unwind in any function
|
||||
// anywhere. We would have to make sure that LLVM only places the
|
||||
// nounwind flag on functions which don't call any other functions.
|
||||
//
|
||||
// 3. The function that overflowed may have owned arguments. These
|
||||
// arguments need to have their destructors run, but we haven't even
|
||||
// begun executing the function yet, so unwinding will not run the
|
||||
// any landing pads for these functions. If this is ignored, then
|
||||
// the arguments will just be leaked.
|
||||
//
|
||||
// Exactly what to do here is a very delicate topic, and is possibly
|
||||
// still up in the air for what exactly to do. Some relevant issues:
|
||||
//
|
||||
// #3555 - out-of-stack failure leaks arguments
|
||||
// #3695 - should there be a stack limit?
|
||||
// #9855 - possible strategies which could be taken
|
||||
// #9854 - unwinding on windows through __morestack has never worked
|
||||
// #2361 - possible implementation of not using landing pads
|
||||
|
||||
if in_green_task_context() {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
let n = task.get()
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|n| n.as_slice())
|
||||
.unwrap_or("<unnamed>");
|
||||
|
||||
// See the message below for why this is not emitted to the
|
||||
// task's logger. This has the additional conundrum of the
|
||||
// logger may not be initialized just yet, meaning that an FFI
|
||||
// call would happen to initialized it (calling out to libuv),
|
||||
// and the FFI call needs 2MB of stack when we just ran out.
|
||||
rterrln!("task '{}' has overflowed its stack", n);
|
||||
} else {
|
||||
rterrln!("stack overflow in non-task context");
|
||||
}
|
||||
|
||||
intrinsics::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the entry point of unwinding for things like lang items and such.
|
||||
/// The arguments are normally generated by the compiler, and need to
|
||||
/// have static lifetimes.
|
||||
pub fn begin_unwind_raw(msg: *c_char, file: *c_char, line: size_t) -> ! {
|
||||
use c_str::CString;
|
||||
use cast::transmute;
|
||||
|
||||
#[inline]
|
||||
fn static_char_ptr(p: *c_char) -> &'static str {
|
||||
let s = unsafe { CString::new(p, false) };
|
||||
match s.as_str() {
|
||||
Some(s) => unsafe { transmute::<&str, &'static str>(s) },
|
||||
None => rtabort!("message wasn't utf8?")
|
||||
}
|
||||
}
|
||||
|
||||
let msg = static_char_ptr(msg);
|
||||
let file = static_char_ptr(file);
|
||||
|
||||
begin_unwind(msg, file, line as uint)
|
||||
}
|
||||
|
||||
/// This is the entry point of unwinding for fail!() and assert!().
|
||||
pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> ! {
|
||||
use any::AnyRefExt;
|
||||
use rt::in_green_task_context;
|
||||
use rt::local::Local;
|
||||
use rt::task::Task;
|
||||
use str::Str;
|
||||
use unstable::intrinsics;
|
||||
|
||||
unsafe {
|
||||
let task: *mut Task;
|
||||
// Note that this should be the only allocation performed in this block.
|
||||
// Currently this means that fail!() on OOM will invoke this code path,
|
||||
// but then again we're not really ready for failing on OOM anyway. If
|
||||
// we do start doing this, then we should propagate this allocation to
|
||||
// be performed in the parent of this task instead of the task that's
|
||||
// failing.
|
||||
let msg = ~msg as ~Any;
|
||||
|
||||
/// Converts one blocked task handle to a list of many handles to the same.
|
||||
pub fn make_selectable(self, num_handles: uint) -> Take<BlockedTaskIterator>
|
||||
{
|
||||
//let msg: &Any = msg;
|
||||
let msg_s = match msg.as_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match msg.as_ref::<~str>() {
|
||||
Some(s) => s.as_slice(),
|
||||
None => "~Any",
|
||||
let arc = match self {
|
||||
Owned(task) => {
|
||||
let flag = unsafe { AtomicUint::new(cast::transmute(task)) };
|
||||
UnsafeArc::new(flag)
|
||||
}
|
||||
Shared(arc) => arc.clone(),
|
||||
};
|
||||
|
||||
if !in_green_task_context() {
|
||||
rterrln!("failed in non-task context at '{}', {}:{}",
|
||||
msg_s, file, line);
|
||||
intrinsics::abort();
|
||||
BlockedTaskIterator{ inner: arc }.take(num_handles)
|
||||
}
|
||||
|
||||
task = Local::unsafe_borrow();
|
||||
let n = (*task).name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
||||
|
||||
// XXX: this should no get forcibly printed to the console, this should
|
||||
// either be sent to the parent task (ideally), or get printed to
|
||||
// the task's logger. Right now the logger is actually a uvio
|
||||
// instance, which uses unkillable blocks internally for various
|
||||
// reasons. This will cause serious trouble if the task is failing
|
||||
// due to mismanagment of its own kill flag, so calling our own
|
||||
// logger in its current state is a bit of a problem.
|
||||
|
||||
rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s, file, line);
|
||||
|
||||
if (*task).unwinder.unwinding {
|
||||
rtabort!("unwinding again");
|
||||
/// Convert to an unsafe uint value. Useful for storing in a pipe's state
|
||||
/// flag.
|
||||
#[inline]
|
||||
pub unsafe fn cast_to_uint(self) -> uint {
|
||||
match self {
|
||||
Owned(task) => {
|
||||
let blocked_task_ptr: uint = cast::transmute(task);
|
||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||
blocked_task_ptr
|
||||
}
|
||||
Shared(arc) => {
|
||||
let blocked_task_ptr: uint = cast::transmute(~arc);
|
||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||
blocked_task_ptr | 0x1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(*task).unwinder.begin_unwind(msg);
|
||||
/// Convert from an unsafe uint value. Useful for retrieving a pipe's state
|
||||
/// flag.
|
||||
#[inline]
|
||||
pub unsafe fn cast_from_uint(blocked_task_ptr: uint) -> BlockedTask {
|
||||
if blocked_task_ptr & 0x1 == 0 {
|
||||
Owned(cast::transmute(blocked_task_ptr))
|
||||
} else {
|
||||
let ptr: ~UnsafeArc<AtomicUint> =
|
||||
cast::transmute(blocked_task_ptr & !1);
|
||||
Shared(*ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Death {
|
||||
pub fn new() -> Death {
|
||||
Death { on_exit: None, }
|
||||
}
|
||||
|
||||
/// Collect failure exit codes from children and propagate them to a parent.
|
||||
pub fn collect_failure(&mut self, result: TaskResult) {
|
||||
match self.on_exit.take() {
|
||||
Some(f) => f(result),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Death {
|
||||
fn drop(&mut self) {
|
||||
// make this type noncopyable
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rt::test::*;
|
||||
use prelude::*;
|
||||
use task;
|
||||
|
||||
#[test]
|
||||
fn local_heap() {
|
||||
do run_in_newsched_task() {
|
||||
let a = @5;
|
||||
let b = a;
|
||||
assert!(*a == 5);
|
||||
assert!(*b == 5);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls() {
|
||||
use local_data;
|
||||
do run_in_newsched_task() {
|
||||
local_data_key!(key: @~str)
|
||||
local_data::set(key, @~"data");
|
||||
assert!(*local_data::get(key, |k| k.map(|k| *k)).unwrap() == ~"data");
|
||||
|
@ -623,59 +436,47 @@ mod test {
|
|||
local_data::set(key2, @~"data");
|
||||
assert!(*local_data::get(key2, |k| k.map(|k| *k)).unwrap() == ~"data");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwind() {
|
||||
do run_in_newsched_task() {
|
||||
let result = spawntask_try(proc()());
|
||||
let result = task::try(proc()());
|
||||
rtdebug!("trying first assert");
|
||||
assert!(result.is_ok());
|
||||
let result = spawntask_try(proc() fail!());
|
||||
let result = task::try::<()>(proc() fail!());
|
||||
rtdebug!("trying second assert");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rng() {
|
||||
do run_in_uv_task() {
|
||||
use rand::{rng, Rng};
|
||||
let mut r = rng();
|
||||
let _ = r.next_u32();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logging() {
|
||||
do run_in_uv_task() {
|
||||
info!("here i am. logging in a newsched task");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comm_stream() {
|
||||
do run_in_newsched_task() {
|
||||
let (port, chan) = Chan::new();
|
||||
chan.send(10);
|
||||
assert!(port.recv() == 10);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comm_shared_chan() {
|
||||
do run_in_newsched_task() {
|
||||
let (port, chan) = SharedChan::new();
|
||||
chan.send(10);
|
||||
assert!(port.recv() == 10);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heap_cycles() {
|
||||
use option::{Option, Some, None};
|
||||
|
||||
do run_in_newsched_task {
|
||||
struct List {
|
||||
next: Option<@mut List>,
|
||||
}
|
||||
|
@ -685,9 +486,20 @@ mod test {
|
|||
|
||||
a.next = Some(b);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_fail]
|
||||
fn test_begin_unwind() { begin_unwind("cause", file!(), line!()) }
|
||||
fn test_begin_unwind() {
|
||||
use rt::unwind::begin_unwind;
|
||||
begin_unwind("cause", file!(), line!())
|
||||
}
|
||||
|
||||
// Task blocking tests
|
||||
|
||||
#[test]
|
||||
fn block_and_wake() {
|
||||
let task = ~Task::new();
|
||||
let mut task = BlockedTask::block(task).wake().unwrap();
|
||||
task.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,440 +0,0 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use io::net::ip::{SocketAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use clone::Clone;
|
||||
use container::Container;
|
||||
use iter::{Iterator, range};
|
||||
use option::{Some, None};
|
||||
use os;
|
||||
use path::GenericPath;
|
||||
use path::Path;
|
||||
use rand::Rng;
|
||||
use rand;
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::basic;
|
||||
use rt::deque::BufferPool;
|
||||
use comm::Chan;
|
||||
use rt::new_event_loop;
|
||||
use rt::sched::Scheduler;
|
||||
use rt::sleeper_list::SleeperList;
|
||||
use rt::task::Task;
|
||||
use rt::thread::Thread;
|
||||
use task::TaskResult;
|
||||
use unstable::{run_in_bare_thread};
|
||||
use vec;
|
||||
use vec::{OwnedVector, MutableVector, ImmutableVector};
|
||||
|
||||
pub fn new_test_uv_sched() -> Scheduler {
|
||||
|
||||
let mut pool = BufferPool::new();
|
||||
let (worker, stealer) = pool.deque();
|
||||
|
||||
let mut sched = Scheduler::new(new_event_loop(),
|
||||
worker,
|
||||
~[stealer],
|
||||
SleeperList::new());
|
||||
|
||||
// Don't wait for the Shutdown message
|
||||
sched.no_sleep = true;
|
||||
return sched;
|
||||
|
||||
}
|
||||
|
||||
pub fn new_test_sched() -> Scheduler {
|
||||
let mut pool = BufferPool::new();
|
||||
let (worker, stealer) = pool.deque();
|
||||
|
||||
let mut sched = Scheduler::new(basic::event_loop(),
|
||||
worker,
|
||||
~[stealer],
|
||||
SleeperList::new());
|
||||
|
||||
// Don't wait for the Shutdown message
|
||||
sched.no_sleep = true;
|
||||
return sched;
|
||||
}
|
||||
|
||||
pub fn run_in_uv_task(f: proc()) {
|
||||
do run_in_bare_thread {
|
||||
run_in_uv_task_core(f);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_in_newsched_task(f: proc()) {
|
||||
do run_in_bare_thread {
|
||||
run_in_newsched_task_core(f);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_in_uv_task_core(f: proc()) {
|
||||
|
||||
use rt::sched::Shutdown;
|
||||
|
||||
let mut sched = ~new_test_uv_sched();
|
||||
let exit_handle = sched.make_handle();
|
||||
|
||||
let on_exit: proc(TaskResult) = proc(exit_status: TaskResult) {
|
||||
let mut exit_handle = exit_handle;
|
||||
exit_handle.send(Shutdown);
|
||||
rtassert!(exit_status.is_ok());
|
||||
};
|
||||
let mut task = ~Task::new_root(&mut sched.stack_pool, None, f);
|
||||
task.death.on_exit = Some(on_exit);
|
||||
|
||||
sched.bootstrap(task);
|
||||
}
|
||||
|
||||
pub fn run_in_newsched_task_core(f: proc()) {
|
||||
use rt::sched::Shutdown;
|
||||
|
||||
let mut sched = ~new_test_sched();
|
||||
let exit_handle = sched.make_handle();
|
||||
|
||||
let on_exit: proc(TaskResult) = proc(exit_status: TaskResult) {
|
||||
let mut exit_handle = exit_handle;
|
||||
exit_handle.send(Shutdown);
|
||||
rtassert!(exit_status.is_ok());
|
||||
};
|
||||
let mut task = ~Task::new_root(&mut sched.stack_pool, None, f);
|
||||
task.death.on_exit = Some(on_exit);
|
||||
|
||||
sched.bootstrap(task);
|
||||
}
|
||||
|
||||
#[cfg(target_os="macos")]
|
||||
#[allow(non_camel_case_types)]
|
||||
mod darwin_fd_limit {
|
||||
/*!
|
||||
* darwin_fd_limit exists to work around an issue where launchctl on Mac OS X defaults the
|
||||
* rlimit maxfiles to 256/unlimited. The default soft limit of 256 ends up being far too low
|
||||
* for our multithreaded scheduler testing, depending on the number of cores available.
|
||||
*
|
||||
* This fixes issue #7772.
|
||||
*/
|
||||
|
||||
use libc;
|
||||
type rlim_t = libc::uint64_t;
|
||||
struct rlimit {
|
||||
rlim_cur: rlim_t,
|
||||
rlim_max: rlim_t
|
||||
}
|
||||
#[nolink]
|
||||
extern {
|
||||
// name probably doesn't need to be mut, but the C function doesn't specify const
|
||||
fn sysctl(name: *mut libc::c_int, namelen: libc::c_uint,
|
||||
oldp: *mut libc::c_void, oldlenp: *mut libc::size_t,
|
||||
newp: *mut libc::c_void, newlen: libc::size_t) -> libc::c_int;
|
||||
fn getrlimit(resource: libc::c_int, rlp: *mut rlimit) -> libc::c_int;
|
||||
fn setrlimit(resource: libc::c_int, rlp: *rlimit) -> libc::c_int;
|
||||
}
|
||||
static CTL_KERN: libc::c_int = 1;
|
||||
static KERN_MAXFILESPERPROC: libc::c_int = 29;
|
||||
static RLIMIT_NOFILE: libc::c_int = 8;
|
||||
|
||||
pub unsafe fn raise_fd_limit() {
|
||||
// The strategy here is to fetch the current resource limits, read the kern.maxfilesperproc
|
||||
// sysctl value, and bump the soft resource limit for maxfiles up to the sysctl value.
|
||||
use ptr::{to_unsafe_ptr, to_mut_unsafe_ptr, mut_null};
|
||||
use mem::size_of_val;
|
||||
use os::last_os_error;
|
||||
|
||||
// Fetch the kern.maxfilesperproc value
|
||||
let mut mib: [libc::c_int, ..2] = [CTL_KERN, KERN_MAXFILESPERPROC];
|
||||
let mut maxfiles: libc::c_int = 0;
|
||||
let mut size: libc::size_t = size_of_val(&maxfiles) as libc::size_t;
|
||||
if sysctl(to_mut_unsafe_ptr(&mut mib[0]), 2,
|
||||
to_mut_unsafe_ptr(&mut maxfiles) as *mut libc::c_void,
|
||||
to_mut_unsafe_ptr(&mut size),
|
||||
mut_null(), 0) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling sysctl: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the current resource limits
|
||||
let mut rlim = rlimit{rlim_cur: 0, rlim_max: 0};
|
||||
if getrlimit(RLIMIT_NOFILE, to_mut_unsafe_ptr(&mut rlim)) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling getrlimit: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bump the soft limit to the smaller of kern.maxfilesperproc and the hard limit
|
||||
rlim.rlim_cur = ::cmp::min(maxfiles as rlim_t, rlim.rlim_max);
|
||||
|
||||
// Set our newly-increased resource limit
|
||||
if setrlimit(RLIMIT_NOFILE, to_unsafe_ptr(&rlim)) != 0 {
|
||||
let err = last_os_error();
|
||||
error!("raise_fd_limit: error calling setrlimit: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os="macos"))]
|
||||
mod darwin_fd_limit {
|
||||
pub unsafe fn raise_fd_limit() {}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn prepare_for_lots_of_tests() {
|
||||
// Bump the fd limit on OS X. See darwin_fd_limit for an explanation.
|
||||
unsafe { darwin_fd_limit::raise_fd_limit() }
|
||||
}
|
||||
|
||||
/// Create more than one scheduler and run a function in a task
|
||||
/// in one of the schedulers. The schedulers will stay alive
|
||||
/// until the function `f` returns.
|
||||
pub fn run_in_mt_newsched_task(f: proc()) {
|
||||
use os;
|
||||
use from_str::FromStr;
|
||||
use rt::sched::Shutdown;
|
||||
use rt::util;
|
||||
|
||||
// see comment in other function (raising fd limits)
|
||||
prepare_for_lots_of_tests();
|
||||
|
||||
do run_in_bare_thread {
|
||||
let nthreads = match os::getenv("RUST_RT_TEST_THREADS") {
|
||||
Some(nstr) => FromStr::from_str(nstr).unwrap(),
|
||||
None => {
|
||||
if util::limit_thread_creation_due_to_osx_and_valgrind() {
|
||||
1
|
||||
} else {
|
||||
// Using more threads than cores in test code
|
||||
// to force the OS to preempt them frequently.
|
||||
// Assuming that this help stress test concurrent types.
|
||||
util::num_cpus() * 2
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let sleepers = SleeperList::new();
|
||||
|
||||
let mut handles = ~[];
|
||||
let mut scheds = ~[];
|
||||
|
||||
let mut pool = BufferPool::<~Task>::new();
|
||||
let workers = range(0, nthreads).map(|_| pool.deque());
|
||||
let (workers, stealers) = vec::unzip(workers);
|
||||
|
||||
for worker in workers.move_iter() {
|
||||
let loop_ = new_event_loop();
|
||||
let mut sched = ~Scheduler::new(loop_,
|
||||
worker,
|
||||
stealers.clone(),
|
||||
sleepers.clone());
|
||||
let handle = sched.make_handle();
|
||||
|
||||
handles.push(handle);
|
||||
scheds.push(sched);
|
||||
}
|
||||
|
||||
let handles = handles; // Work around not being able to capture mut
|
||||
let on_exit: proc(TaskResult) = proc(exit_status: TaskResult) {
|
||||
// Tell schedulers to exit
|
||||
let mut handles = handles;
|
||||
for handle in handles.mut_iter() {
|
||||
handle.send(Shutdown);
|
||||
}
|
||||
|
||||
rtassert!(exit_status.is_ok());
|
||||
};
|
||||
let mut main_task = ~Task::new_root(&mut scheds[0].stack_pool,
|
||||
None,
|
||||
f);
|
||||
main_task.death.on_exit = Some(on_exit);
|
||||
|
||||
let mut threads = ~[];
|
||||
|
||||
let main_thread = {
|
||||
let sched = scheds.pop();
|
||||
let main_task = main_task;
|
||||
do Thread::start {
|
||||
sched.bootstrap(main_task);
|
||||
}
|
||||
};
|
||||
threads.push(main_thread);
|
||||
|
||||
while !scheds.is_empty() {
|
||||
let mut sched = scheds.pop();
|
||||
let bootstrap_task = ~do Task::new_root(&mut sched.stack_pool, None) || {
|
||||
rtdebug!("bootstrapping non-primary scheduler");
|
||||
};
|
||||
let sched = sched;
|
||||
let thread = do Thread::start {
|
||||
sched.bootstrap(bootstrap_task);
|
||||
};
|
||||
|
||||
threads.push(thread);
|
||||
}
|
||||
|
||||
// Wait for schedulers
|
||||
for thread in threads.move_iter() {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Test tasks will abort on failure instead of unwinding
|
||||
pub fn spawntask(f: proc()) {
|
||||
Scheduler::run_task(Task::build_child(None, f));
|
||||
}
|
||||
|
||||
/// Create a new task and run it right now. Aborts on failure
|
||||
pub fn spawntask_later(f: proc()) {
|
||||
Scheduler::run_task_later(Task::build_child(None, f));
|
||||
}
|
||||
|
||||
pub fn spawntask_random(f: proc()) {
|
||||
use rand::{Rand, rng};
|
||||
|
||||
let mut rng = rng();
|
||||
let run_now: bool = Rand::rand(&mut rng);
|
||||
|
||||
if run_now {
|
||||
spawntask(f)
|
||||
} else {
|
||||
spawntask_later(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawntask_try(f: proc()) -> Result<(),()> {
|
||||
|
||||
let (port, chan) = Chan::new();
|
||||
let on_exit: proc(TaskResult) = proc(exit_status) {
|
||||
chan.send(exit_status)
|
||||
};
|
||||
|
||||
let mut new_task = Task::build_root(None, f);
|
||||
new_task.death.on_exit = Some(on_exit);
|
||||
|
||||
Scheduler::run_task(new_task);
|
||||
|
||||
let exit_status = port.recv();
|
||||
if exit_status.is_ok() { Ok(()) } else { Err(()) }
|
||||
|
||||
}
|
||||
|
||||
/// Spawn a new task in a new scheduler and return a thread handle.
|
||||
pub fn spawntask_thread(f: proc()) -> Thread<()> {
|
||||
let thread = do Thread::start {
|
||||
run_in_newsched_task_core(f);
|
||||
};
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
/// Get a ~Task for testing purposes other than actually scheduling it.
|
||||
pub fn with_test_task(blk: proc(~Task) -> ~Task) {
|
||||
do run_in_bare_thread {
|
||||
let mut sched = ~new_test_sched();
|
||||
let task = blk(~Task::new_root(&mut sched.stack_pool,
|
||||
None,
|
||||
proc() {}));
|
||||
cleanup_task(task);
|
||||
}
|
||||
}
|
||||
|
||||
/// Use to cleanup tasks created for testing but not "run".
|
||||
pub fn cleanup_task(mut task: ~Task) {
|
||||
task.destroyed = true;
|
||||
}
|
||||
|
||||
/// Get a port number, starting at 9600, for use in tests
|
||||
pub fn next_test_port() -> u16 {
|
||||
use unstable::mutex::{Mutex, MUTEX_INIT};
|
||||
static mut lock: Mutex = MUTEX_INIT;
|
||||
static mut next_offset: u16 = 0;
|
||||
unsafe {
|
||||
let base = base_port();
|
||||
lock.lock();
|
||||
let ret = base + next_offset;
|
||||
next_offset += 1;
|
||||
lock.unlock();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a temporary path which could be the location of a unix socket
|
||||
pub fn next_test_unix() -> Path {
|
||||
if cfg!(unix) {
|
||||
os::tmpdir().join(rand::task_rng().gen_ascii_str(20))
|
||||
} else {
|
||||
Path::new(r"\\.\pipe\" + rand::task_rng().gen_ascii_str(20))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a unique IPv4 localhost:port pair starting at 9600
|
||||
pub fn next_test_ip4() -> SocketAddr {
|
||||
SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: next_test_port() }
|
||||
}
|
||||
|
||||
/// Get a unique IPv6 localhost:port pair starting at 9600
|
||||
pub fn next_test_ip6() -> SocketAddr {
|
||||
SocketAddr { ip: Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1), port: next_test_port() }
|
||||
}
|
||||
|
||||
/*
|
||||
XXX: Welcome to MegaHack City.
|
||||
|
||||
The bots run multiple builds at the same time, and these builds
|
||||
all want to use ports. This function figures out which workspace
|
||||
it is running in and assigns a port range based on it.
|
||||
*/
|
||||
fn base_port() -> u16 {
|
||||
use os;
|
||||
use str::StrSlice;
|
||||
use vec::ImmutableVector;
|
||||
|
||||
let base = 9600u16;
|
||||
let range = 1000u16;
|
||||
|
||||
let bases = [
|
||||
("32-opt", base + range * 1),
|
||||
("32-noopt", base + range * 2),
|
||||
("64-opt", base + range * 3),
|
||||
("64-noopt", base + range * 4),
|
||||
("64-opt-vg", base + range * 5),
|
||||
("all-opt", base + range * 6),
|
||||
("snap3", base + range * 7),
|
||||
("dist", base + range * 8)
|
||||
];
|
||||
|
||||
// FIXME (#9639): This needs to handle non-utf8 paths
|
||||
let path = os::getcwd();
|
||||
let path_s = path.as_str().unwrap();
|
||||
|
||||
let mut final_base = base;
|
||||
|
||||
for &(dir, base) in bases.iter() {
|
||||
if path_s.contains(dir) {
|
||||
final_base = base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return final_base;
|
||||
}
|
||||
|
||||
/// Get a constant that represents the number of times to repeat
|
||||
/// stress tests. Default 1.
|
||||
pub fn stress_factor() -> uint {
|
||||
use os::getenv;
|
||||
use from_str::from_str;
|
||||
|
||||
match getenv("RUST_RT_STRESS") {
|
||||
Some(val) => from_str::<uint>(val).unwrap(),
|
||||
None => 1
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ pub struct Thread<T> {
|
|||
priv packet: ~Option<T>,
|
||||
}
|
||||
|
||||
static DEFAULT_STACK_SIZE: libc::size_t = 1024 * 1024;
|
||||
static DEFAULT_STACK_SIZE: uint = 1024 * 1024;
|
||||
|
||||
// This is the starting point of rust os threads. The first thing we do
|
||||
// is make sure that we don't trigger __morestack (also why this has a
|
||||
|
@ -41,9 +41,9 @@ static DEFAULT_STACK_SIZE: libc::size_t = 1024 * 1024;
|
|||
// and invoke it.
|
||||
#[no_split_stack]
|
||||
extern fn thread_start(main: *libc::c_void) -> imp::rust_thread_return {
|
||||
use rt::context;
|
||||
use unstable::stack;
|
||||
unsafe {
|
||||
context::record_stack_bounds(0, uint::max_value);
|
||||
stack::record_stack_bounds(0, uint::max_value);
|
||||
let f: ~proc() = cast::transmute(main);
|
||||
(*f)();
|
||||
cast::transmute(0 as imp::rust_thread_return)
|
||||
|
@ -69,6 +69,12 @@ impl Thread<()> {
|
|||
/// called, when the `Thread` falls out of scope its destructor will block
|
||||
/// waiting for the OS thread.
|
||||
pub fn start<T: Send>(main: proc() -> T) -> Thread<T> {
|
||||
Thread::start_stack(DEFAULT_STACK_SIZE, main)
|
||||
}
|
||||
|
||||
/// Performs the same functionality as `start`, but specifies an explicit
|
||||
/// stack size for the new thread.
|
||||
pub fn start_stack<T: Send>(stack: uint, main: proc() -> T) -> Thread<T> {
|
||||
|
||||
// We need the address of the packet to fill in to be stable so when
|
||||
// `main` fills it in it's still valid, so allocate an extra ~ box to do
|
||||
|
@ -78,7 +84,7 @@ impl Thread<()> {
|
|||
*cast::transmute::<&~Option<T>, **mut Option<T>>(&packet)
|
||||
};
|
||||
let main: proc() = proc() unsafe { *packet2 = Some(main()); };
|
||||
let native = unsafe { imp::create(~main) };
|
||||
let native = unsafe { imp::create(stack, ~main) };
|
||||
|
||||
Thread {
|
||||
native: native,
|
||||
|
@ -94,8 +100,14 @@ impl Thread<()> {
|
|||
/// systems. Note that platforms may not keep the main program alive even if
|
||||
/// there are detached thread still running around.
|
||||
pub fn spawn(main: proc()) {
|
||||
Thread::spawn_stack(DEFAULT_STACK_SIZE, main)
|
||||
}
|
||||
|
||||
/// Performs the same functionality as `spawn`, but explicitly specifies a
|
||||
/// stack size for the new thread.
|
||||
pub fn spawn_stack(stack: uint, main: proc()) {
|
||||
unsafe {
|
||||
let handle = imp::create(~main);
|
||||
let handle = imp::create(stack, ~main);
|
||||
imp::detach(handle);
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +144,6 @@ impl<T: Send> Drop for Thread<T> {
|
|||
|
||||
#[cfg(windows)]
|
||||
mod imp {
|
||||
use super::DEFAULT_STACK_SIZE;
|
||||
|
||||
use cast;
|
||||
use libc;
|
||||
use libc::types::os::arch::extra::{LPSECURITY_ATTRIBUTES, SIZE_T, BOOL,
|
||||
|
@ -143,9 +153,9 @@ mod imp {
|
|||
pub type rust_thread = HANDLE;
|
||||
pub type rust_thread_return = DWORD;
|
||||
|
||||
pub unsafe fn create(p: ~proc()) -> rust_thread {
|
||||
pub unsafe fn create(stack: uint, p: ~proc()) -> rust_thread {
|
||||
let arg: *mut libc::c_void = cast::transmute(p);
|
||||
CreateThread(ptr::mut_null(), DEFAULT_STACK_SIZE, super::thread_start,
|
||||
CreateThread(ptr::mut_null(), stack as libc::size_t, super::thread_start,
|
||||
arg, 0, ptr::mut_null())
|
||||
}
|
||||
|
||||
|
@ -183,17 +193,17 @@ mod imp {
|
|||
use libc::consts::os::posix01::PTHREAD_CREATE_JOINABLE;
|
||||
use libc;
|
||||
use ptr;
|
||||
use super::DEFAULT_STACK_SIZE;
|
||||
use unstable::intrinsics;
|
||||
|
||||
pub type rust_thread = libc::pthread_t;
|
||||
pub type rust_thread_return = *libc::c_void;
|
||||
|
||||
pub unsafe fn create(p: ~proc()) -> rust_thread {
|
||||
pub unsafe fn create(stack: uint, p: ~proc()) -> rust_thread {
|
||||
let mut native: libc::pthread_t = intrinsics::uninit();
|
||||
let mut attr: libc::pthread_attr_t = intrinsics::uninit();
|
||||
assert_eq!(pthread_attr_init(&mut attr), 0);
|
||||
assert_eq!(pthread_attr_setstacksize(&mut attr, DEFAULT_STACK_SIZE), 0);
|
||||
assert_eq!(pthread_attr_setstacksize(&mut attr,
|
||||
stack as libc::size_t), 0);
|
||||
assert_eq!(pthread_attr_setdetachstate(&mut attr,
|
||||
PTHREAD_CREATE_JOINABLE), 0);
|
||||
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! A very simple unsynchronized channel type for sending buffered data from
|
||||
//! scheduler context to task context.
|
||||
//!
|
||||
//! XXX: This would be safer to use if split into two types like Port/Chan
|
||||
|
||||
use option::*;
|
||||
use clone::Clone;
|
||||
use super::rc::RC;
|
||||
use rt::sched::Scheduler;
|
||||
use rt::kill::BlockedTask;
|
||||
use rt::local::Local;
|
||||
use vec::OwnedVector;
|
||||
use container::Container;
|
||||
|
||||
struct TubeState<T> {
|
||||
blocked_task: Option<BlockedTask>,
|
||||
buf: ~[T]
|
||||
}
|
||||
|
||||
pub struct Tube<T> {
|
||||
priv p: RC<TubeState<T>>
|
||||
}
|
||||
|
||||
impl<T> Tube<T> {
|
||||
pub fn new() -> Tube<T> {
|
||||
Tube {
|
||||
p: RC::new(TubeState {
|
||||
blocked_task: None,
|
||||
buf: ~[]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, val: T) {
|
||||
rtdebug!("tube send");
|
||||
unsafe {
|
||||
let state = self.p.unsafe_borrow_mut();
|
||||
(*state).buf.push(val);
|
||||
|
||||
if (*state).blocked_task.is_some() {
|
||||
// There's a waiting task. Wake it up
|
||||
rtdebug!("waking blocked tube");
|
||||
let task = (*state).blocked_task.take_unwrap();
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.resume_blocked_task_immediately(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) -> T {
|
||||
unsafe {
|
||||
let state = self.p.unsafe_borrow_mut();
|
||||
if !(*state).buf.is_empty() {
|
||||
return (*state).buf.shift();
|
||||
} else {
|
||||
// Block and wait for the next message
|
||||
rtdebug!("blocking on tube recv");
|
||||
assert!(self.p.refcount() > 1); // There better be somebody to wake us up
|
||||
assert!((*state).blocked_task.is_none());
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|_, task| {
|
||||
(*state).blocked_task = Some(task);
|
||||
});
|
||||
rtdebug!("waking after tube recv");
|
||||
let buf = &mut (*state).buf;
|
||||
assert!(!buf.is_empty());
|
||||
return buf.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Tube<T> {
|
||||
fn clone(&self) -> Tube<T> {
|
||||
Tube { p: self.p.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rt::test::*;
|
||||
use rt::rtio::EventLoop;
|
||||
use rt::sched::Scheduler;
|
||||
use rt::local::Local;
|
||||
use super::*;
|
||||
use prelude::*;
|
||||
|
||||
#[test]
|
||||
fn simple_test() {
|
||||
do run_in_newsched_task {
|
||||
let mut tube: Tube<int> = Tube::new();
|
||||
let mut tube_clone = Some(tube.clone());
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|sched, task| {
|
||||
let mut tube_clone = tube_clone.take_unwrap();
|
||||
tube_clone.send(1);
|
||||
sched.enqueue_blocked_task(task);
|
||||
});
|
||||
|
||||
assert!(tube.recv() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_test() {
|
||||
do run_in_newsched_task {
|
||||
let mut tube: Tube<int> = Tube::new();
|
||||
let mut tube_clone = Some(tube.clone());
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|sched, task| {
|
||||
let tube_clone = tube_clone.take_unwrap();
|
||||
do sched.event_loop.callback {
|
||||
let mut tube_clone = tube_clone;
|
||||
// The task should be blocked on this now and
|
||||
// sending will wake it up.
|
||||
tube_clone.send(1);
|
||||
}
|
||||
sched.enqueue_blocked_task(task);
|
||||
});
|
||||
|
||||
assert!(tube.recv() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_blocking_test() {
|
||||
static MAX: int = 100;
|
||||
|
||||
do run_in_newsched_task {
|
||||
let mut tube: Tube<int> = Tube::new();
|
||||
let mut tube_clone = Some(tube.clone());
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.deschedule_running_task_and_then(|sched, task| {
|
||||
callback_send(tube_clone.take_unwrap(), 0);
|
||||
|
||||
fn callback_send(tube: Tube<int>, i: int) {
|
||||
if i == 100 {
|
||||
return
|
||||
}
|
||||
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
do sched.get().event_loop.callback {
|
||||
let mut tube = tube;
|
||||
// The task should be blocked on this now and
|
||||
// sending will wake it up.
|
||||
tube.send(i);
|
||||
callback_send(tube, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
sched.enqueue_blocked_task(task);
|
||||
});
|
||||
|
||||
for i in range(0, MAX) {
|
||||
let j = tube.recv();
|
||||
assert!(j == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,11 +8,11 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
// Implementation of Rust stack unwinding
|
||||
//
|
||||
// For background on exception handling and stack unwinding please see "Exception Handling in LLVM"
|
||||
// (llvm.org/docs/ExceptionHandling.html) and documents linked from it.
|
||||
// For background on exception handling and stack unwinding please see
|
||||
// "Exception Handling in LLVM" (llvm.org/docs/ExceptionHandling.html) and
|
||||
// documents linked from it.
|
||||
// These are also good reads:
|
||||
// http://theofilos.cs.columbia.edu/blog/2013/09/22/base_abi/
|
||||
// http://monoinfinito.wordpress.com/series/exception-handling-in-c/
|
||||
|
@ -21,41 +21,55 @@
|
|||
// ~~~ A brief summary ~~~
|
||||
// Exception handling happens in two phases: a search phase and a cleanup phase.
|
||||
//
|
||||
// In both phases the unwinder walks stack frames from top to bottom using information from
|
||||
// the stack frame unwind sections of the current process's modules ("module" here refers to
|
||||
// an OS module, i.e. an executable or a dynamic library).
|
||||
// In both phases the unwinder walks stack frames from top to bottom using
|
||||
// information from the stack frame unwind sections of the current process's
|
||||
// modules ("module" here refers to an OS module, i.e. an executable or a
|
||||
// dynamic library).
|
||||
//
|
||||
// For each stack frame, it invokes the associated "personality routine", whose address is also
|
||||
// stored in the unwind info section.
|
||||
// For each stack frame, it invokes the associated "personality routine", whose
|
||||
// address is also stored in the unwind info section.
|
||||
//
|
||||
// In the search phase, the job of a personality routine is to examine exception object being
|
||||
// thrown, and to decide whether it should be caught at that stack frame. Once the handler frame
|
||||
// has been identified, cleanup phase begins.
|
||||
// In the search phase, the job of a personality routine is to examine exception
|
||||
// object being thrown, and to decide whether it should be caught at that stack
|
||||
// frame. Once the handler frame has been identified, cleanup phase begins.
|
||||
//
|
||||
// In the cleanup phase, personality routines invoke cleanup code associated with their
|
||||
// stack frames (i.e. destructors). Once stack has been unwound down to the handler frame level,
|
||||
// unwinding stops and the last personality routine transfers control to its' catch block.
|
||||
// In the cleanup phase, personality routines invoke cleanup code associated
|
||||
// with their stack frames (i.e. destructors). Once stack has been unwound down
|
||||
// to the handler frame level, unwinding stops and the last personality routine
|
||||
// transfers control to its' catch block.
|
||||
//
|
||||
// ~~~ Frame unwind info registration ~~~
|
||||
// Each module has its' own frame unwind info section (usually ".eh_frame"), and unwinder needs
|
||||
// to know about all of them in order for unwinding to be able to cross module boundaries.
|
||||
// Each module has its' own frame unwind info section (usually ".eh_frame"), and
|
||||
// unwinder needs to know about all of them in order for unwinding to be able to
|
||||
// cross module boundaries.
|
||||
//
|
||||
// On some platforms, like Linux, this is achieved by dynamically enumerating currently loaded
|
||||
// modules via the dl_iterate_phdr() API and finding all .eh_frame sections.
|
||||
// On some platforms, like Linux, this is achieved by dynamically enumerating
|
||||
// currently loaded modules via the dl_iterate_phdr() API and finding all
|
||||
// .eh_frame sections.
|
||||
//
|
||||
// Others, like Windows, require modules to actively register their unwind info sections by calling
|
||||
// __register_frame_info() API at startup.
|
||||
// In the latter case it is essential that there is only one copy of the unwinder runtime
|
||||
// in the process. This is usually achieved by linking to the dynamic version of the unwind
|
||||
// runtime.
|
||||
// Others, like Windows, require modules to actively register their unwind info
|
||||
// sections by calling __register_frame_info() API at startup. In the latter
|
||||
// case it is essential that there is only one copy of the unwinder runtime in
|
||||
// the process. This is usually achieved by linking to the dynamic version of
|
||||
// the unwind runtime.
|
||||
//
|
||||
// Currently Rust uses unwind runtime provided by libgcc.
|
||||
|
||||
use prelude::*;
|
||||
use cast::transmute;
|
||||
use task::TaskResult;
|
||||
use any::{Any, AnyRefExt};
|
||||
use c_str::CString;
|
||||
use cast;
|
||||
use kinds::Send;
|
||||
use libc::{c_char, size_t};
|
||||
use libc::{c_void, c_int};
|
||||
use self::libunwind::*;
|
||||
use option::{Some, None, Option};
|
||||
use result::{Err, Ok};
|
||||
use rt::local::Local;
|
||||
use rt::task::Task;
|
||||
use str::Str;
|
||||
use task::TaskResult;
|
||||
use unstable::intrinsics;
|
||||
|
||||
use uw = self::libunwind;
|
||||
|
||||
mod libunwind {
|
||||
//! Unwind library interface
|
||||
|
@ -110,34 +124,41 @@ mod libunwind {
|
|||
}
|
||||
|
||||
pub struct Unwinder {
|
||||
unwinding: bool,
|
||||
cause: Option<~Any>
|
||||
priv unwinding: bool,
|
||||
priv cause: Option<~Any>
|
||||
}
|
||||
|
||||
impl Unwinder {
|
||||
pub fn new() -> Unwinder {
|
||||
Unwinder {
|
||||
unwinding: false,
|
||||
cause: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwinding(&self) -> bool {
|
||||
self.unwinding
|
||||
}
|
||||
|
||||
pub fn try(&mut self, f: ||) {
|
||||
use unstable::raw::Closure;
|
||||
|
||||
unsafe {
|
||||
let closure: Closure = transmute(f);
|
||||
let code = transmute(closure.code);
|
||||
let env = transmute(closure.env);
|
||||
|
||||
let ep = rust_try(try_fn, code, env);
|
||||
let closure: Closure = cast::transmute(f);
|
||||
let ep = rust_try(try_fn, closure.code as *c_void,
|
||||
closure.env as *c_void);
|
||||
if !ep.is_null() {
|
||||
rtdebug!("Caught {}", (*ep).exception_class);
|
||||
_Unwind_DeleteException(ep);
|
||||
uw::_Unwind_DeleteException(ep);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn try_fn(code: *c_void, env: *c_void) {
|
||||
unsafe {
|
||||
let closure: Closure = Closure {
|
||||
code: transmute(code),
|
||||
env: transmute(env),
|
||||
};
|
||||
let closure: || = transmute(closure);
|
||||
let closure: || = cast::transmute(Closure {
|
||||
code: code as *(),
|
||||
env: env as *(),
|
||||
});
|
||||
closure();
|
||||
}
|
||||
}
|
||||
|
@ -145,10 +166,11 @@ impl Unwinder {
|
|||
extern {
|
||||
// Rust's try-catch
|
||||
// When f(...) returns normally, the return value is null.
|
||||
// When f(...) throws, the return value is a pointer to the caught exception object.
|
||||
// When f(...) throws, the return value is a pointer to the caught
|
||||
// exception object.
|
||||
fn rust_try(f: extern "C" fn(*c_void, *c_void),
|
||||
code: *c_void,
|
||||
data: *c_void) -> *_Unwind_Exception;
|
||||
data: *c_void) -> *uw::_Unwind_Exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,21 +181,21 @@ impl Unwinder {
|
|||
self.cause = Some(cause);
|
||||
|
||||
unsafe {
|
||||
let exception = ~_Unwind_Exception {
|
||||
let exception = ~uw::_Unwind_Exception {
|
||||
exception_class: rust_exception_class(),
|
||||
exception_cleanup: exception_cleanup,
|
||||
private_1: 0,
|
||||
private_2: 0
|
||||
};
|
||||
let error = _Unwind_RaiseException(transmute(exception));
|
||||
let error = uw::_Unwind_RaiseException(cast::transmute(exception));
|
||||
rtabort!("Could not unwind stack, error = {}", error as int)
|
||||
}
|
||||
|
||||
extern "C" fn exception_cleanup(_unwind_code: _Unwind_Reason_Code,
|
||||
exception: *_Unwind_Exception) {
|
||||
extern "C" fn exception_cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
||||
exception: *uw::_Unwind_Exception) {
|
||||
rtdebug!("exception_cleanup()");
|
||||
unsafe {
|
||||
let _: ~_Unwind_Exception = transmute(exception);
|
||||
let _: ~uw::_Unwind_Exception = cast::transmute(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,68 +211,146 @@ impl Unwinder {
|
|||
|
||||
// Rust's exception class identifier. This is used by personality routines to
|
||||
// determine whether the exception was thrown by their own runtime.
|
||||
fn rust_exception_class() -> _Unwind_Exception_Class {
|
||||
let bytes = bytes!("MOZ\0RUST"); // vendor, language
|
||||
unsafe {
|
||||
let ptr: *_Unwind_Exception_Class = transmute(bytes.as_ptr());
|
||||
*ptr
|
||||
}
|
||||
fn rust_exception_class() -> uw::_Unwind_Exception_Class {
|
||||
// M O Z \0 R U S T -- vendor, language
|
||||
0x4d4f5a_00_52555354
|
||||
}
|
||||
|
||||
|
||||
// We could implement our personality routine in pure Rust, however exception info decoding
|
||||
// is tedious. More importantly, personality routines have to handle various platform
|
||||
// quirks, which are not fun to maintain. For this reason, we attempt to reuse personality
|
||||
// routine of the C language: __gcc_personality_v0.
|
||||
// We could implement our personality routine in pure Rust, however exception
|
||||
// info decoding is tedious. More importantly, personality routines have to
|
||||
// handle various platform quirks, which are not fun to maintain. For this
|
||||
// reason, we attempt to reuse personality routine of the C language:
|
||||
// __gcc_personality_v0.
|
||||
//
|
||||
// Since C does not support exception catching, __gcc_personality_v0 simply always
|
||||
// returns _URC_CONTINUE_UNWIND in search phase, and always returns _URC_INSTALL_CONTEXT
|
||||
// (i.e. "invoke cleanup code") in cleanup phase.
|
||||
// Since C does not support exception catching, __gcc_personality_v0 simply
|
||||
// always returns _URC_CONTINUE_UNWIND in search phase, and always returns
|
||||
// _URC_INSTALL_CONTEXT (i.e. "invoke cleanup code") in cleanup phase.
|
||||
//
|
||||
// This is pretty close to Rust's exception handling approach, except that Rust does have
|
||||
// a single "catch-all" handler at the bottom of each task's stack.
|
||||
// This is pretty close to Rust's exception handling approach, except that Rust
|
||||
// does have a single "catch-all" handler at the bottom of each task's stack.
|
||||
// So we have two versions:
|
||||
// - rust_eh_personality, used by all cleanup landing pads, which never catches, so
|
||||
// the behavior of __gcc_personality_v0 is perfectly adequate there, and
|
||||
// - rust_eh_personality_catch, used only by rust_try(), which always catches. This is
|
||||
// achieved by overriding the return value in search phase to always say "catch!".
|
||||
// - rust_eh_personality, used by all cleanup landing pads, which never catches,
|
||||
// so the behavior of __gcc_personality_v0 is perfectly adequate there, and
|
||||
// - rust_eh_personality_catch, used only by rust_try(), which always catches.
|
||||
// This is achieved by overriding the return value in search phase to always
|
||||
// say "catch!".
|
||||
|
||||
extern "C" {
|
||||
fn __gcc_personality_v0(version: c_int,
|
||||
actions: _Unwind_Action,
|
||||
exception_class: _Unwind_Exception_Class,
|
||||
ue_header: *_Unwind_Exception,
|
||||
context: *_Unwind_Context) -> _Unwind_Reason_Code;
|
||||
actions: uw::_Unwind_Action,
|
||||
exception_class: uw::_Unwind_Exception_Class,
|
||||
ue_header: *uw::_Unwind_Exception,
|
||||
context: *uw::_Unwind_Context)
|
||||
-> uw::_Unwind_Reason_Code;
|
||||
}
|
||||
|
||||
#[lang="eh_personality"]
|
||||
#[no_mangle] // so we can reference it by name from middle/trans/base.rs
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(test))]
|
||||
pub extern "C" fn rust_eh_personality(version: c_int,
|
||||
actions: _Unwind_Action,
|
||||
exception_class: _Unwind_Exception_Class,
|
||||
ue_header: *_Unwind_Exception,
|
||||
context: *_Unwind_Context) -> _Unwind_Reason_Code {
|
||||
pub extern "C" fn rust_eh_personality(
|
||||
version: c_int,
|
||||
actions: uw::_Unwind_Action,
|
||||
exception_class: uw::_Unwind_Exception_Class,
|
||||
ue_header: *uw::_Unwind_Exception,
|
||||
context: *uw::_Unwind_Context
|
||||
) -> uw::_Unwind_Reason_Code
|
||||
{
|
||||
unsafe {
|
||||
__gcc_personality_v0(version, actions, exception_class, ue_header, context)
|
||||
__gcc_personality_v0(version, actions, exception_class, ue_header,
|
||||
context)
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle] // referenced from rust_try.ll
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(test))]
|
||||
pub extern "C" fn rust_eh_personality_catch(version: c_int,
|
||||
actions: _Unwind_Action,
|
||||
exception_class: _Unwind_Exception_Class,
|
||||
ue_header: *_Unwind_Exception,
|
||||
context: *_Unwind_Context) -> _Unwind_Reason_Code {
|
||||
if (actions as c_int & _UA_SEARCH_PHASE as c_int) != 0 { // search phase
|
||||
_URC_HANDLER_FOUND // catch!
|
||||
pub extern "C" fn rust_eh_personality_catch(
|
||||
version: c_int,
|
||||
actions: uw::_Unwind_Action,
|
||||
exception_class: uw::_Unwind_Exception_Class,
|
||||
ue_header: *uw::_Unwind_Exception,
|
||||
context: *uw::_Unwind_Context
|
||||
) -> uw::_Unwind_Reason_Code
|
||||
{
|
||||
if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { // search phase
|
||||
uw::_URC_HANDLER_FOUND // catch!
|
||||
}
|
||||
else { // cleanup phase
|
||||
unsafe {
|
||||
__gcc_personality_v0(version, actions, exception_class, ue_header, context)
|
||||
__gcc_personality_v0(version, actions, exception_class, ue_header,
|
||||
context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the entry point of unwinding for things like lang items and such.
|
||||
/// The arguments are normally generated by the compiler, and need to
|
||||
/// have static lifetimes.
|
||||
pub fn begin_unwind_raw(msg: *c_char, file: *c_char, line: size_t) -> ! {
|
||||
#[inline]
|
||||
fn static_char_ptr(p: *c_char) -> &'static str {
|
||||
let s = unsafe { CString::new(p, false) };
|
||||
match s.as_str() {
|
||||
Some(s) => unsafe { cast::transmute::<&str, &'static str>(s) },
|
||||
None => rtabort!("message wasn't utf8?")
|
||||
}
|
||||
}
|
||||
|
||||
let msg = static_char_ptr(msg);
|
||||
let file = static_char_ptr(file);
|
||||
|
||||
begin_unwind(msg, file, line as uint)
|
||||
}
|
||||
|
||||
/// This is the entry point of unwinding for fail!() and assert!().
|
||||
pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> ! {
|
||||
unsafe {
|
||||
let task: *mut Task;
|
||||
// Note that this should be the only allocation performed in this block.
|
||||
// Currently this means that fail!() on OOM will invoke this code path,
|
||||
// but then again we're not really ready for failing on OOM anyway. If
|
||||
// we do start doing this, then we should propagate this allocation to
|
||||
// be performed in the parent of this task instead of the task that's
|
||||
// failing.
|
||||
let msg = ~msg as ~Any;
|
||||
|
||||
{
|
||||
let msg_s = match msg.as_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match msg.as_ref::<~str>() {
|
||||
Some(s) => s.as_slice(),
|
||||
None => "~Any",
|
||||
}
|
||||
};
|
||||
|
||||
// It is assumed that all reasonable rust code will have a local
|
||||
// task at all times. This means that this `try_unsafe_borrow` will
|
||||
// succeed almost all of the time. There are border cases, however,
|
||||
// when the runtime has *almost* set up the local task, but hasn't
|
||||
// quite gotten there yet. In order to get some better diagnostics,
|
||||
// we print on failure and immediately abort the whole process if
|
||||
// there is no local task available.
|
||||
match Local::try_unsafe_borrow() {
|
||||
Some(t) => {
|
||||
task = t;
|
||||
let n = (*task).name.as_ref()
|
||||
.map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
||||
|
||||
rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s,
|
||||
file, line);
|
||||
}
|
||||
None => {
|
||||
rterrln!("failed at '{}', {}:{}", msg_s, file, line);
|
||||
intrinsics::abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (*task).unwinder.unwinding {
|
||||
rtabort!("unwinding again");
|
||||
}
|
||||
}
|
||||
|
||||
(*task).unwinder.begin_unwind(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ use libc;
|
|||
use option::{Some, None, Option};
|
||||
use os;
|
||||
use str::StrSlice;
|
||||
use unstable::atomics::{AtomicInt, INIT_ATOMIC_INT, SeqCst};
|
||||
use unstable::running_on_valgrind;
|
||||
|
||||
// Indicates whether we should perform expensive sanity checks, including rtassert!
|
||||
|
@ -68,11 +67,21 @@ pub fn default_sched_threads() -> uint {
|
|||
}
|
||||
|
||||
pub fn dumb_println(args: &fmt::Arguments) {
|
||||
use io::native::file::FileDesc;
|
||||
use io;
|
||||
use libc;
|
||||
let mut out = FileDesc::new(libc::STDERR_FILENO, false);
|
||||
fmt::writeln(&mut out as &mut io::Writer, args);
|
||||
|
||||
struct Stderr;
|
||||
impl io::Writer for Stderr {
|
||||
fn write(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
libc::write(libc::STDERR_FILENO,
|
||||
data.as_ptr() as *libc::c_void,
|
||||
data.len() as libc::size_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut w = Stderr;
|
||||
fmt::writeln(&mut w as &mut io::Writer, args);
|
||||
}
|
||||
|
||||
pub fn abort(msg: &str) -> ! {
|
||||
|
@ -133,13 +142,3 @@ memory and partly incapable of presentation to others.",
|
|||
unsafe { libc::abort() }
|
||||
}
|
||||
}
|
||||
|
||||
static mut EXIT_STATUS: AtomicInt = INIT_ATOMIC_INT;
|
||||
|
||||
pub fn set_exit_status(code: int) {
|
||||
unsafe { EXIT_STATUS.store(code, SeqCst) }
|
||||
}
|
||||
|
||||
pub fn get_exit_status() -> int {
|
||||
unsafe { EXIT_STATUS.load(SeqCst) }
|
||||
}
|
||||
|
|
|
@ -338,8 +338,8 @@ mod tests {
|
|||
use str;
|
||||
use task::spawn;
|
||||
use unstable::running_on_valgrind;
|
||||
use io::native::file;
|
||||
use io::{FileNotFound, Reader, Writer, io_error};
|
||||
use io::pipe::PipeStream;
|
||||
use io::{Writer, Reader, io_error, FileNotFound, OtherIoError};
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os="android"))] // FIXME(#10380)
|
||||
|
@ -426,13 +426,13 @@ mod tests {
|
|||
}
|
||||
|
||||
fn writeclose(fd: c_int, s: &str) {
|
||||
let mut writer = file::FileDesc::new(fd, true);
|
||||
let mut writer = PipeStream::open(fd);
|
||||
writer.write(s.as_bytes());
|
||||
}
|
||||
|
||||
fn readclose(fd: c_int) -> ~str {
|
||||
let mut res = ~[];
|
||||
let mut reader = file::FileDesc::new(fd, true);
|
||||
let mut reader = PipeStream::open(fd);
|
||||
let mut buf = [0, ..1024];
|
||||
loop {
|
||||
match reader.read(buf) {
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Atomically reference counted data
|
||||
//!
|
||||
//! This modules contains the implementation of an atomically reference counted
|
||||
//! pointer for the purpose of sharing data between tasks. This is obviously a
|
||||
//! very unsafe primitive to use, but it has its use cases when implementing
|
||||
//! concurrent data structures and similar tasks.
|
||||
//!
|
||||
//! Great care must be taken to ensure that data races do not arise through the
|
||||
//! usage of `UnsafeArc`, and this often requires some form of external
|
||||
//! synchronization. The only guarantee provided to you by this class is that
|
||||
//! the underlying data will remain valid (not free'd) so long as the reference
|
||||
//! count is greater than one.
|
||||
|
||||
use cast;
|
||||
use clone::Clone;
|
||||
use kinds::Send;
|
||||
use ops::Drop;
|
||||
use ptr::RawPtr;
|
||||
use sync::atomics::{AtomicUint, SeqCst, Relaxed, Acquire};
|
||||
use vec;
|
||||
|
||||
/// An atomically reference counted pointer.
|
||||
///
|
||||
/// Enforces no shared-memory safety.
|
||||
#[unsafe_no_drop_flag]
|
||||
pub struct UnsafeArc<T> {
|
||||
priv data: *mut ArcData<T>,
|
||||
}
|
||||
|
||||
struct ArcData<T> {
|
||||
count: AtomicUint,
|
||||
data: T,
|
||||
}
|
||||
|
||||
unsafe fn new_inner<T: Send>(data: T, refcount: uint) -> *mut ArcData<T> {
|
||||
let data = ~ArcData { count: AtomicUint::new(refcount), data: data };
|
||||
cast::transmute(data)
|
||||
}
|
||||
|
||||
impl<T: Send> UnsafeArc<T> {
|
||||
/// Creates a new `UnsafeArc` which wraps the given data.
|
||||
pub fn new(data: T) -> UnsafeArc<T> {
|
||||
unsafe { UnsafeArc { data: new_inner(data, 1) } }
|
||||
}
|
||||
|
||||
/// As new(), but returns an extra pre-cloned handle.
|
||||
pub fn new2(data: T) -> (UnsafeArc<T>, UnsafeArc<T>) {
|
||||
unsafe {
|
||||
let ptr = new_inner(data, 2);
|
||||
(UnsafeArc { data: ptr }, UnsafeArc { data: ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// As new(), but returns a vector of as many pre-cloned handles as
|
||||
/// requested.
|
||||
pub fn newN(data: T, num_handles: uint) -> ~[UnsafeArc<T>] {
|
||||
unsafe {
|
||||
if num_handles == 0 {
|
||||
~[] // need to free data here
|
||||
} else {
|
||||
let ptr = new_inner(data, num_handles);
|
||||
vec::from_fn(num_handles, |_| UnsafeArc { data: ptr })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a pointer to the inner shared data. Note that care must be taken to
|
||||
/// ensure that the outer `UnsafeArc` does not fall out of scope while this
|
||||
/// pointer is in use, otherwise it could possibly contain a use-after-free.
|
||||
#[inline]
|
||||
pub fn get(&self) -> *mut T {
|
||||
unsafe {
|
||||
assert!((*self.data).count.load(Relaxed) > 0);
|
||||
return &mut (*self.data).data as *mut T;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets an immutable pointer to the inner shared data. This has the same
|
||||
/// caveats as the `get` method.
|
||||
#[inline]
|
||||
pub fn get_immut(&self) -> *T {
|
||||
unsafe {
|
||||
assert!((*self.data).count.load(Relaxed) > 0);
|
||||
return &(*self.data).data as *T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> Clone for UnsafeArc<T> {
|
||||
fn clone(&self) -> UnsafeArc<T> {
|
||||
unsafe {
|
||||
// This barrier might be unnecessary, but I'm not sure...
|
||||
let old_count = (*self.data).count.fetch_add(1, Acquire);
|
||||
assert!(old_count >= 1);
|
||||
return UnsafeArc { data: self.data };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe_destructor]
|
||||
impl<T> Drop for UnsafeArc<T>{
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Happens when destructing an unwrapper's handle and from
|
||||
// `#[unsafe_no_drop_flag]`
|
||||
if self.data.is_null() {
|
||||
return
|
||||
}
|
||||
// Must be acquire+release, not just release, to make sure this
|
||||
// doesn't get reordered to after the unwrapper pointer load.
|
||||
let old_count = (*self.data).count.fetch_sub(1, SeqCst);
|
||||
assert!(old_count >= 1);
|
||||
if old_count == 1 {
|
||||
let _: ~ArcData<T> = cast::transmute(self.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use prelude::*;
|
||||
use super::UnsafeArc;
|
||||
use mem::size_of;
|
||||
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_eq!(size_of::<UnsafeArc<[int, ..10]>>(), size_of::<*[int, ..10]>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arclike_newN() {
|
||||
// Tests that the many-refcounts-at-once constructors don't leak.
|
||||
let _ = UnsafeArc::new2(~~"hello");
|
||||
let x = UnsafeArc::newN(~~"hello", 0);
|
||||
assert_eq!(x.len(), 0)
|
||||
let x = UnsafeArc::newN(~~"hello", 1);
|
||||
assert_eq!(x.len(), 1)
|
||||
let x = UnsafeArc::newN(~~"hello", 10);
|
||||
assert_eq!(x.len(), 10)
|
||||
}
|
||||
}
|
|
@ -11,13 +11,16 @@
|
|||
/*!
|
||||
* Atomic types
|
||||
*
|
||||
* Basic atomic types supporting atomic operations. Each method takes an `Ordering` which
|
||||
* represents the strength of the memory barrier for that operation. These orderings are the same
|
||||
* as C++11 atomic orderings [http://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync]
|
||||
* Basic atomic types supporting atomic operations. Each method takes an
|
||||
* `Ordering` which represents the strength of the memory barrier for that
|
||||
* operation. These orderings are the same as C++11 atomic orderings
|
||||
* [http://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync]
|
||||
*
|
||||
* All atomic types are a single word in size.
|
||||
*/
|
||||
|
||||
#[allow(missing_doc)];
|
||||
|
||||
use unstable::intrinsics;
|
||||
use cast;
|
||||
use option::{Option,Some,None};
|
|
@ -50,15 +50,18 @@
|
|||
|
||||
use cast;
|
||||
use clone::Clone;
|
||||
use iter::range;
|
||||
use iter::{range, Iterator};
|
||||
use kinds::Send;
|
||||
use libc;
|
||||
use mem;
|
||||
use ops::Drop;
|
||||
use option::{Option, Some, None};
|
||||
use ptr;
|
||||
use unstable::atomics::{AtomicInt, AtomicPtr, SeqCst};
|
||||
use unstable::sync::{UnsafeArc, Exclusive};
|
||||
use ptr::RawPtr;
|
||||
use sync::arc::UnsafeArc;
|
||||
use sync::atomics::{AtomicInt, AtomicPtr, SeqCst};
|
||||
use unstable::sync::Exclusive;
|
||||
use vec::{OwnedVector, ImmutableVector};
|
||||
|
||||
// Once the queue is less than 1/K full, then it will be downsized. Note that
|
||||
// the deque requires that this number be less than 2.
|
||||
|
@ -399,7 +402,7 @@ mod tests {
|
|||
use rt::thread::Thread;
|
||||
use rand;
|
||||
use rand::Rng;
|
||||
use unstable::atomics::{AtomicBool, INIT_ATOMIC_BOOL, SeqCst,
|
||||
use sync::atomics::{AtomicBool, INIT_ATOMIC_BOOL, SeqCst,
|
||||
AtomicUint, INIT_ATOMIC_UINT};
|
||||
use vec;
|
||||
|
|
@ -8,19 +8,16 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// xfail-fast make-check does not like `#[start]`
|
||||
//! Useful synchronization primitives
|
||||
//!
|
||||
//! This modules contains useful safe and unsafe synchronization primitives.
|
||||
//! Most of the primitives in this module do not provide any sort of locking
|
||||
//! and/or blocking at all, but rather provide the necessary tools to build
|
||||
//! other types of concurrent primitives.
|
||||
|
||||
use std::rt;
|
||||
|
||||
#[start]
|
||||
fn start(argc: int, argv: **u8) -> int {
|
||||
do rt::start(argc, argv) {
|
||||
println("First invocation");
|
||||
};
|
||||
|
||||
do rt::start(argc, argv) {
|
||||
println("Second invocation");
|
||||
};
|
||||
|
||||
0
|
||||
}
|
||||
pub mod arc;
|
||||
pub mod atomics;
|
||||
pub mod deque;
|
||||
pub mod mpmc_bounded_queue;
|
||||
pub mod mpsc_queue;
|
||||
pub mod spsc_queue;
|
|
@ -25,15 +25,17 @@
|
|||
* policies, either expressed or implied, of Dmitry Vyukov.
|
||||
*/
|
||||
|
||||
#[allow(missing_doc, dead_code)];
|
||||
|
||||
// http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue
|
||||
|
||||
use unstable::sync::UnsafeArc;
|
||||
use unstable::atomics::{AtomicUint,Relaxed,Release,Acquire};
|
||||
use option::*;
|
||||
use vec;
|
||||
use clone::Clone;
|
||||
use kinds::Send;
|
||||
use num::{Exponential,Algebraic,Round};
|
||||
use option::{Option, Some, None};
|
||||
use sync::arc::UnsafeArc;
|
||||
use sync::atomics::{AtomicUint,Relaxed,Release,Acquire};
|
||||
use vec;
|
||||
|
||||
struct Node<T> {
|
||||
sequence: AtomicUint,
|
||||
|
@ -161,8 +163,8 @@ impl<T: Send> Clone for Queue<T> {
|
|||
mod tests {
|
||||
use prelude::*;
|
||||
use option::*;
|
||||
use task;
|
||||
use super::Queue;
|
||||
use native;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
|
@ -170,14 +172,17 @@ mod tests {
|
|||
let nmsgs = 1000u;
|
||||
let mut q = Queue::with_capacity(nthreads*nmsgs);
|
||||
assert_eq!(None, q.pop());
|
||||
let (port, chan) = SharedChan::new();
|
||||
|
||||
for _ in range(0, nthreads) {
|
||||
let q = q.clone();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let chan = chan.clone();
|
||||
do native::task::spawn {
|
||||
let mut q = q;
|
||||
for i in range(0, nmsgs) {
|
||||
assert!(q.push(i));
|
||||
}
|
||||
chan.send(());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +191,7 @@ mod tests {
|
|||
let (completion_port, completion_chan) = Chan::new();
|
||||
completion_ports.push(completion_port);
|
||||
let q = q.clone();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
do native::task::spawn {
|
||||
let mut q = q;
|
||||
let mut i = 0u;
|
||||
loop {
|
||||
|
@ -205,5 +210,8 @@ mod tests {
|
|||
for completion_port in completion_ports.mut_iter() {
|
||||
assert_eq!(nmsgs, completion_port.recv());
|
||||
}
|
||||
for _ in range(0, nthreads) {
|
||||
port.recv();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,14 @@
|
|||
*/
|
||||
|
||||
//! A mostly lock-free multi-producer, single consumer queue.
|
||||
//!
|
||||
//! This module contains an implementation of a concurrent MPSC queue. This
|
||||
//! queue can be used to share data between tasks, and is also used as the
|
||||
//! building block of channels in rust.
|
||||
//!
|
||||
//! Note that the current implementation of this queue has a caveat of the `pop`
|
||||
//! method, and see the method for more information about it. Due to this
|
||||
//! caveat, this queue may not be appropriate for all use-cases.
|
||||
|
||||
// http://www.1024cores.net/home/lock-free-algorithms
|
||||
// /queues/non-intrusive-mpsc-node-based-queue
|
||||
|
@ -35,9 +43,11 @@ use clone::Clone;
|
|||
use kinds::Send;
|
||||
use ops::Drop;
|
||||
use option::{Option, None, Some};
|
||||
use unstable::atomics::{AtomicPtr, Release, Acquire, AcqRel, Relaxed};
|
||||
use unstable::sync::UnsafeArc;
|
||||
use ptr::RawPtr;
|
||||
use sync::arc::UnsafeArc;
|
||||
use sync::atomics::{AtomicPtr, Release, Acquire, AcqRel, Relaxed};
|
||||
|
||||
/// A result of the `pop` function.
|
||||
pub enum PopResult<T> {
|
||||
/// Some data has been popped
|
||||
Data(T),
|
||||
|
@ -61,10 +71,14 @@ struct State<T, P> {
|
|||
packet: P,
|
||||
}
|
||||
|
||||
/// The consumer half of this concurrent queue. This half is used to receive
|
||||
/// data from the producers.
|
||||
pub struct Consumer<T, P> {
|
||||
priv state: UnsafeArc<State<T, P>>,
|
||||
}
|
||||
|
||||
/// The production half of the concurrent queue. This handle may be cloned in
|
||||
/// order to make handles for new producers.
|
||||
pub struct Producer<T, P> {
|
||||
priv state: UnsafeArc<State<T, P>>,
|
||||
}
|
||||
|
@ -75,6 +89,11 @@ impl<T: Send, P: Send> Clone for Producer<T, P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new MPSC queue. The given argument `p` is a user-defined "packet"
|
||||
/// of information which will be shared by the consumer and the producer which
|
||||
/// can be re-acquired via the `packet` function. This is helpful when extra
|
||||
/// state is shared between the producer and consumer, but note that there is no
|
||||
/// synchronization performed of this data.
|
||||
pub fn queue<T: Send, P: Send>(p: P) -> (Consumer<T, P>, Producer<T, P>) {
|
||||
unsafe {
|
||||
let (a, b) = UnsafeArc::new2(State::new(p));
|
||||
|
@ -92,7 +111,7 @@ impl<T> Node<T> {
|
|||
}
|
||||
|
||||
impl<T: Send, P: Send> State<T, P> {
|
||||
pub unsafe fn new(p: P) -> State<T, P> {
|
||||
unsafe fn new(p: P) -> State<T, P> {
|
||||
let stub = Node::new(None);
|
||||
State {
|
||||
head: AtomicPtr::new(stub),
|
||||
|
@ -122,10 +141,6 @@ impl<T: Send, P: Send> State<T, P> {
|
|||
|
||||
if self.head.load(Acquire) == tail {Empty} else {Inconsistent}
|
||||
}
|
||||
|
||||
unsafe fn is_empty(&mut self) -> bool {
|
||||
return (*self.tail).next.load(Acquire).is_null();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe_destructor]
|
||||
|
@ -143,27 +158,42 @@ impl<T: Send, P: Send> Drop for State<T, P> {
|
|||
}
|
||||
|
||||
impl<T: Send, P: Send> Producer<T, P> {
|
||||
/// Pushes a new value onto this queue.
|
||||
pub fn push(&mut self, value: T) {
|
||||
unsafe { (*self.state.get()).push(value) }
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
unsafe{ (*self.state.get()).is_empty() }
|
||||
}
|
||||
/// Gets an unsafe pointer to the user-defined packet shared by the
|
||||
/// producers and the consumer. Note that care must be taken to ensure that
|
||||
/// the lifetime of the queue outlives the usage of the returned pointer.
|
||||
pub unsafe fn packet(&self) -> *mut P {
|
||||
&mut (*self.state.get()).packet as *mut P
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, P: Send> Consumer<T, P> {
|
||||
/// Pops some data from this queue.
|
||||
///
|
||||
/// Note that the current implementation means that this function cannot
|
||||
/// return `Option<T>`. It is possible for this queue to be in an
|
||||
/// inconsistent state where many pushes have suceeded and completely
|
||||
/// finished, but pops cannot return `Some(t)`. This inconsistent state
|
||||
/// happens when a pusher is pre-empted at an inopportune moment.
|
||||
///
|
||||
/// This inconsistent state means that this queue does indeed have data, but
|
||||
/// it does not currently have access to it at this time.
|
||||
pub fn pop(&mut self) -> PopResult<T> {
|
||||
unsafe { (*self.state.get()).pop() }
|
||||
}
|
||||
/// Attempts to pop data from this queue, but doesn't attempt too hard. This
|
||||
/// will canonicalize inconsistent states to a `None` value.
|
||||
pub fn casual_pop(&mut self) -> Option<T> {
|
||||
match self.pop() {
|
||||
Data(t) => Some(t),
|
||||
Empty | Inconsistent => None,
|
||||
}
|
||||
}
|
||||
/// Gets an unsafe pointer to the underlying user-defined packet. See
|
||||
/// `Producer.packet` for more information.
|
||||
pub unsafe fn packet(&self) -> *mut P {
|
||||
&mut (*self.state.get()).packet as *mut P
|
||||
}
|
||||
|
@ -173,8 +203,8 @@ impl<T: Send, P: Send> Consumer<T, P> {
|
|||
mod tests {
|
||||
use prelude::*;
|
||||
|
||||
use task;
|
||||
use super::{queue, Data, Empty, Inconsistent};
|
||||
use native;
|
||||
|
||||
#[test]
|
||||
fn test_full() {
|
||||
|
@ -192,14 +222,17 @@ mod tests {
|
|||
Empty => {}
|
||||
Inconsistent | Data(..) => fail!()
|
||||
}
|
||||
let (port, chan) = SharedChan::new();
|
||||
|
||||
for _ in range(0, nthreads) {
|
||||
let q = p.clone();
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let chan = chan.clone();
|
||||
do native::task::spawn {
|
||||
let mut q = q;
|
||||
for i in range(0, nmsgs) {
|
||||
q.push(i);
|
||||
}
|
||||
chan.send(());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,6 +243,9 @@ mod tests {
|
|||
Data(_) => { i += 1 }
|
||||
}
|
||||
}
|
||||
for _ in range(0, nthreads) {
|
||||
port.recv();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,12 +26,20 @@
|
|||
*/
|
||||
|
||||
// http://www.1024cores.net/home/lock-free-algorithms/queues/unbounded-spsc-queue
|
||||
|
||||
//! A single-producer single-consumer concurrent queue
|
||||
//!
|
||||
//! This module contains the implementation of an SPSC queue which can be used
|
||||
//! concurrently between two tasks. This data structure is safe to use and
|
||||
//! enforces the semantics that there is one pusher and one popper.
|
||||
|
||||
use cast;
|
||||
use kinds::Send;
|
||||
use ops::Drop;
|
||||
use option::{Some, None, Option};
|
||||
use unstable::atomics::{AtomicPtr, Relaxed, AtomicUint, Acquire, Release};
|
||||
use unstable::sync::UnsafeArc;
|
||||
use ptr::RawPtr;
|
||||
use sync::arc::UnsafeArc;
|
||||
use sync::atomics::{AtomicPtr, Relaxed, AtomicUint, Acquire, Release};
|
||||
|
||||
// Node within the linked list queue of messages to send
|
||||
struct Node<T> {
|
||||
|
@ -64,14 +72,34 @@ struct State<T, P> {
|
|||
packet: P,
|
||||
}
|
||||
|
||||
/// Producer half of this queue. This handle is used to push data to the
|
||||
/// consumer.
|
||||
pub struct Producer<T, P> {
|
||||
priv state: UnsafeArc<State<T, P>>,
|
||||
}
|
||||
|
||||
/// Consumer half of this queue. This handle is used to receive data from the
|
||||
/// producer.
|
||||
pub struct Consumer<T, P> {
|
||||
priv state: UnsafeArc<State<T, P>>,
|
||||
}
|
||||
|
||||
/// Creates a new queue. The producer returned is connected to the consumer to
|
||||
/// push all data to the consumer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bound` - This queue implementation is implemented with a linked list,
|
||||
/// and this means that a push is always a malloc. In order to
|
||||
/// amortize this cost, an internal cache of nodes is maintained
|
||||
/// to prevent a malloc from always being necessary. This bound is
|
||||
/// the limit on the size of the cache (if desired). If the value
|
||||
/// is 0, then the cache has no bound. Otherwise, the cache will
|
||||
/// never grow larger than `bound` (although the queue itself
|
||||
/// could be much larger.
|
||||
///
|
||||
/// * `p` - This is the user-defined packet of data which will also be shared
|
||||
/// between the producer and consumer.
|
||||
pub fn queue<T: Send, P: Send>(bound: uint,
|
||||
p: P) -> (Consumer<T, P>, Producer<T, P>)
|
||||
{
|
||||
|
@ -105,21 +133,31 @@ impl<T: Send> Node<T> {
|
|||
}
|
||||
|
||||
impl<T: Send, P: Send> Producer<T, P> {
|
||||
/// Pushes data onto the queue
|
||||
pub fn push(&mut self, t: T) {
|
||||
unsafe { (*self.state.get()).push(t) }
|
||||
}
|
||||
/// Tests whether the queue is empty. Note that if this function returns
|
||||
/// `false`, the return value is significant, but if the return value is
|
||||
/// `true` then almost no meaning can be attached to the return value.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
unsafe { (*self.state.get()).is_empty() }
|
||||
}
|
||||
/// Acquires an unsafe pointer to the underlying user-defined packet. Note
|
||||
/// that care must be taken to ensure that the queue outlives the usage of
|
||||
/// the packet (because it is an unsafe pointer).
|
||||
pub unsafe fn packet(&self) -> *mut P {
|
||||
&mut (*self.state.get()).packet as *mut P
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, P: Send> Consumer<T, P> {
|
||||
/// Pops some data from this queue, returning `None` when the queue is
|
||||
/// empty.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
unsafe { (*self.state.get()).pop() }
|
||||
}
|
||||
/// Same function as the producer's `packet` method.
|
||||
pub unsafe fn packet(&self) -> *mut P {
|
||||
&mut (*self.state.get()).packet as *mut P
|
||||
}
|
||||
|
@ -230,7 +268,7 @@ impl<T: Send, P: Send> Drop for State<T, P> {
|
|||
mod test {
|
||||
use prelude::*;
|
||||
use super::queue;
|
||||
use task;
|
||||
use native;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
|
@ -276,7 +314,8 @@ mod test {
|
|||
|
||||
fn stress_bound(bound: uint) {
|
||||
let (c, mut p) = queue(bound, ());
|
||||
do task::spawn_sched(task::SingleThreaded) {
|
||||
let (port, chan) = Chan::new();
|
||||
do native::task::spawn {
|
||||
let mut c = c;
|
||||
for _ in range(0, 100000) {
|
||||
loop {
|
||||
|
@ -287,10 +326,12 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
chan.send(());
|
||||
}
|
||||
for _ in range(0, 100000) {
|
||||
p.push(1);
|
||||
}
|
||||
port.recv();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,22 +53,22 @@
|
|||
|
||||
#[allow(missing_doc)];
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use any::Any;
|
||||
use comm::{Chan, Port};
|
||||
use kinds::Send;
|
||||
use option::{None, Some, Option};
|
||||
use result::{Result, Ok, Err};
|
||||
use rt::in_green_task_context;
|
||||
use rt::local::Local;
|
||||
use rt::task::Task;
|
||||
use send_str::{SendStr, IntoSendStr};
|
||||
use str::Str;
|
||||
use util;
|
||||
|
||||
#[cfg(test)] use any::Any;
|
||||
#[cfg(test)] use any::{AnyOwnExt, AnyRefExt};
|
||||
#[cfg(test)] use comm::SharedChan;
|
||||
#[cfg(test)] use ptr;
|
||||
#[cfg(test)] use result;
|
||||
|
||||
pub mod spawn;
|
||||
|
||||
/// Indicates the manner in which a task exited.
|
||||
///
|
||||
/// A task that completes without failing is considered to exit successfully.
|
||||
|
@ -80,27 +80,6 @@ pub mod spawn;
|
|||
/// children tasks complete, recommend using a result future.
|
||||
pub type TaskResult = Result<(), ~Any>;
|
||||
|
||||
/// Scheduler modes
|
||||
#[deriving(Eq)]
|
||||
pub enum SchedMode {
|
||||
/// Run task on the default scheduler
|
||||
DefaultScheduler,
|
||||
/// All tasks run in the same OS thread
|
||||
SingleThreaded,
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler configuration options
|
||||
*
|
||||
* # Fields
|
||||
*
|
||||
* * sched_mode - The operating mode of the scheduler
|
||||
*
|
||||
*/
|
||||
pub struct SchedOpts {
|
||||
priv mode: SchedMode,
|
||||
}
|
||||
|
||||
/**
|
||||
* Task configuration options
|
||||
*
|
||||
|
@ -121,10 +100,9 @@ pub struct SchedOpts {
|
|||
* scheduler other tasks will be impeded or even blocked indefinitely.
|
||||
*/
|
||||
pub struct TaskOpts {
|
||||
priv watched: bool,
|
||||
priv notify_chan: Option<Chan<TaskResult>>,
|
||||
watched: bool,
|
||||
notify_chan: Option<Chan<TaskResult>>,
|
||||
name: Option<SendStr>,
|
||||
sched: SchedOpts,
|
||||
stack_size: Option<uint>
|
||||
}
|
||||
|
||||
|
@ -153,7 +131,7 @@ pub struct TaskBuilder {
|
|||
*/
|
||||
pub fn task() -> TaskBuilder {
|
||||
TaskBuilder {
|
||||
opts: default_task_opts(),
|
||||
opts: TaskOpts::new(),
|
||||
gen_body: None,
|
||||
can_not_copy: None,
|
||||
}
|
||||
|
@ -169,7 +147,6 @@ impl TaskBuilder {
|
|||
watched: self.opts.watched,
|
||||
notify_chan: notify_chan,
|
||||
name: name,
|
||||
sched: self.opts.sched,
|
||||
stack_size: self.opts.stack_size
|
||||
},
|
||||
gen_body: gen_body,
|
||||
|
@ -229,11 +206,6 @@ impl TaskBuilder {
|
|||
self.opts.name = Some(name.into_send_str());
|
||||
}
|
||||
|
||||
/// Configure a custom scheduler mode for the task.
|
||||
pub fn sched_mode(&mut self, mode: SchedMode) {
|
||||
self.opts.sched.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a wrapper to the body of the spawned task.
|
||||
*
|
||||
|
@ -285,7 +257,6 @@ impl TaskBuilder {
|
|||
watched: x.opts.watched,
|
||||
notify_chan: notify_chan,
|
||||
name: name,
|
||||
sched: x.opts.sched,
|
||||
stack_size: x.opts.stack_size
|
||||
};
|
||||
let f = match gen_body {
|
||||
|
@ -296,7 +267,9 @@ impl TaskBuilder {
|
|||
f
|
||||
}
|
||||
};
|
||||
spawn::spawn_raw(opts, f);
|
||||
|
||||
let t: ~Task = Local::take();
|
||||
t.spawn_sibling(opts, f);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -328,10 +301,10 @@ impl TaskBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* Task construction */
|
||||
|
||||
pub fn default_task_opts() -> TaskOpts {
|
||||
impl TaskOpts {
|
||||
pub fn new() -> TaskOpts {
|
||||
/*!
|
||||
* The default task options
|
||||
*
|
||||
|
@ -343,12 +316,10 @@ pub fn default_task_opts() -> TaskOpts {
|
|||
watched: true,
|
||||
notify_chan: None,
|
||||
name: None,
|
||||
sched: SchedOpts {
|
||||
mode: DefaultScheduler,
|
||||
},
|
||||
stack_size: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Spawn convenience functions */
|
||||
|
||||
|
@ -363,24 +334,6 @@ pub fn spawn(f: proc()) {
|
|||
task.spawn(f)
|
||||
}
|
||||
|
||||
pub fn spawn_sched(mode: SchedMode, f: proc()) {
|
||||
/*!
|
||||
* Creates a new task on a new or existing scheduler.
|
||||
*
|
||||
* When there are no more tasks to execute the
|
||||
* scheduler terminates.
|
||||
*
|
||||
* # Failure
|
||||
*
|
||||
* In manual threads mode the number of threads requested must be
|
||||
* greater than zero.
|
||||
*/
|
||||
|
||||
let mut task = task();
|
||||
task.sched_mode(mode);
|
||||
task.spawn(f)
|
||||
}
|
||||
|
||||
pub fn try<T:Send>(f: proc() -> T) -> Result<T, ~Any> {
|
||||
/*!
|
||||
* Execute a function in another task and return either the return value
|
||||
|
@ -400,26 +353,21 @@ pub fn try<T:Send>(f: proc() -> T) -> Result<T, ~Any> {
|
|||
pub fn with_task_name<U>(blk: |Option<&str>| -> U) -> U {
|
||||
use rt::task::Task;
|
||||
|
||||
if in_green_task_context() {
|
||||
let mut task = Local::borrow(None::<Task>);
|
||||
match task.get().name {
|
||||
Some(ref name) => blk(Some(name.as_slice())),
|
||||
None => blk(None)
|
||||
}
|
||||
} else {
|
||||
fail!("no task name exists in non-green task context")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deschedule() {
|
||||
//! Yield control to the task scheduler
|
||||
|
||||
use rt::local::Local;
|
||||
use rt::sched::Scheduler;
|
||||
|
||||
// FIXME(#7544): Optimize this, since we know we won't block.
|
||||
let sched: ~Scheduler = Local::take();
|
||||
sched.yield_now();
|
||||
let task: ~Task = Local::take();
|
||||
task.yield_now();
|
||||
}
|
||||
|
||||
pub fn failing() -> bool {
|
||||
|
@ -428,7 +376,7 @@ pub fn failing() -> bool {
|
|||
use rt::task::Task;
|
||||
|
||||
let mut local = Local::borrow(None::<Task>);
|
||||
local.get().unwinder.unwinding
|
||||
local.get().unwinder.unwinding()
|
||||
}
|
||||
|
||||
// The following 8 tests test the following 2^3 combinations:
|
||||
|
@ -439,22 +387,15 @@ pub fn failing() -> bool {
|
|||
|
||||
#[test]
|
||||
fn test_unnamed_task() {
|
||||
use rt::test::run_in_uv_task;
|
||||
|
||||
do run_in_uv_task {
|
||||
do spawn {
|
||||
with_task_name(|name| {
|
||||
assert!(name.is_none());
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owned_named_task() {
|
||||
use rt::test::run_in_uv_task;
|
||||
|
||||
do run_in_uv_task {
|
||||
let mut t = task();
|
||||
t.name(~"ada lovelace");
|
||||
do t.spawn {
|
||||
|
@ -463,13 +404,9 @@ fn test_owned_named_task() {
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_named_task() {
|
||||
use rt::test::run_in_uv_task;
|
||||
|
||||
do run_in_uv_task {
|
||||
let mut t = task();
|
||||
t.name("ada lovelace");
|
||||
do t.spawn {
|
||||
|
@ -478,13 +415,9 @@ fn test_static_named_task() {
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_named_task() {
|
||||
use rt::test::run_in_uv_task;
|
||||
|
||||
do run_in_uv_task {
|
||||
let mut t = task();
|
||||
t.name("ada lovelace".into_send_str());
|
||||
do t.spawn {
|
||||
|
@ -493,7 +426,6 @@ fn test_send_named_task() {
|
|||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_basic() {
|
||||
|
@ -562,28 +494,19 @@ fn test_try_fail() {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_sched_id() -> int {
|
||||
use rt::sched::Scheduler;
|
||||
let mut sched = Local::borrow(None::<Scheduler>);
|
||||
sched.get().sched_id() as int
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_sched() {
|
||||
use clone::Clone;
|
||||
|
||||
let (po, ch) = SharedChan::new();
|
||||
|
||||
fn f(i: int, ch: SharedChan<()>) {
|
||||
let parent_sched_id = get_sched_id();
|
||||
|
||||
do spawn_sched(SingleThreaded) {
|
||||
let child_sched_id = get_sched_id();
|
||||
assert!(parent_sched_id != child_sched_id);
|
||||
|
||||
let ch = ch.clone();
|
||||
do spawn {
|
||||
if (i == 0) {
|
||||
ch.send(());
|
||||
} else {
|
||||
f(i - 1, ch.clone());
|
||||
f(i - 1, ch);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -596,16 +519,9 @@ fn test_spawn_sched() {
|
|||
fn test_spawn_sched_childs_on_default_sched() {
|
||||
let (po, ch) = Chan::new();
|
||||
|
||||
// Assuming tests run on the default scheduler
|
||||
let default_id = get_sched_id();
|
||||
|
||||
do spawn_sched(SingleThreaded) {
|
||||
let ch = ch;
|
||||
let parent_sched_id = get_sched_id();
|
||||
do spawn {
|
||||
let child_sched_id = get_sched_id();
|
||||
assert!(parent_sched_id != child_sched_id);
|
||||
assert_eq!(child_sched_id, default_id);
|
||||
let ch = ch;
|
||||
do spawn {
|
||||
ch.send(());
|
||||
};
|
||||
};
|
||||
|
@ -613,65 +529,6 @@ fn test_spawn_sched_childs_on_default_sched() {
|
|||
po.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_sched_blocking() {
|
||||
use unstable::mutex::Mutex;
|
||||
|
||||
unsafe {
|
||||
|
||||
// Testing that a task in one scheduler can block in foreign code
|
||||
// without affecting other schedulers
|
||||
20u.times(|| {
|
||||
let (start_po, start_ch) = Chan::new();
|
||||
let (fin_po, fin_ch) = Chan::new();
|
||||
|
||||
let mut lock = Mutex::new();
|
||||
let lock2 = lock.clone();
|
||||
|
||||
do spawn_sched(SingleThreaded) {
|
||||
let mut lock = lock2;
|
||||
lock.lock();
|
||||
|
||||
start_ch.send(());
|
||||
|
||||
// Block the scheduler thread
|
||||
lock.wait();
|
||||
lock.unlock();
|
||||
|
||||
fin_ch.send(());
|
||||
};
|
||||
|
||||
// Wait until the other task has its lock
|
||||
start_po.recv();
|
||||
|
||||
fn pingpong(po: &Port<int>, ch: &Chan<int>) {
|
||||
let mut val = 20;
|
||||
while val > 0 {
|
||||
val = po.recv();
|
||||
ch.try_send(val - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let (setup_po, setup_ch) = Chan::new();
|
||||
let (parent_po, parent_ch) = Chan::new();
|
||||
do spawn {
|
||||
let (child_po, child_ch) = Chan::new();
|
||||
setup_ch.send(child_ch);
|
||||
pingpong(&child_po, &parent_ch);
|
||||
};
|
||||
|
||||
let child_ch = setup_po.recv();
|
||||
child_ch.send(20);
|
||||
pingpong(&parent_po, &child_ch);
|
||||
lock.lock();
|
||||
lock.signal();
|
||||
lock.unlock();
|
||||
fin_po.recv();
|
||||
lock.destroy();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn avoid_copying_the_body(spawnfn: |v: proc()|) {
|
||||
let (p, ch) = Chan::<uint>::new();
|
||||
|
@ -735,12 +592,8 @@ fn test_child_doesnt_ref_parent() {
|
|||
|
||||
#[test]
|
||||
fn test_simple_newsched_spawn() {
|
||||
use rt::test::run_in_uv_task;
|
||||
|
||||
do run_in_uv_task {
|
||||
spawn(proc()())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_fail_message_static_str() {
|
|
@ -1,233 +0,0 @@
|
|||
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
/*!**************************************************************************
|
||||
*
|
||||
* WARNING: linked failure has been removed since this doc comment was written,
|
||||
* but it was so pretty that I didn't want to remove it.
|
||||
*
|
||||
* Spawning & linked failure
|
||||
*
|
||||
* Several data structures are involved in task management to allow properly
|
||||
* propagating failure across linked/supervised tasks.
|
||||
*
|
||||
* (1) The "taskgroup_arc" is an unsafe::exclusive which contains a hashset of
|
||||
* all tasks that are part of the group. Some tasks are 'members', which
|
||||
* means if they fail, they will kill everybody else in the taskgroup.
|
||||
* Other tasks are 'descendants', which means they will not kill tasks
|
||||
* from this group, but can be killed by failing members.
|
||||
*
|
||||
* A new one of these is created each spawn_linked or spawn_supervised.
|
||||
*
|
||||
* (2) The "taskgroup" is a per-task control structure that tracks a task's
|
||||
* spawn configuration. It contains a reference to its taskgroup_arc, a
|
||||
* reference to its node in the ancestor list (below), and an optionally
|
||||
* configured notification port. These are stored in TLS.
|
||||
*
|
||||
* (3) The "ancestor_list" is a cons-style list of unsafe::exclusives which
|
||||
* tracks 'generations' of taskgroups -- a group's ancestors are groups
|
||||
* which (directly or transitively) spawn_supervised-ed them. Each task
|
||||
* is recorded in the 'descendants' of each of its ancestor groups.
|
||||
*
|
||||
* Spawning a supervised task is O(n) in the number of generations still
|
||||
* alive, and exiting (by success or failure) that task is also O(n).
|
||||
*
|
||||
* This diagram depicts the references between these data structures:
|
||||
*
|
||||
* linked_________________________________
|
||||
* ___/ _________ \___
|
||||
* / \ | group X | / \
|
||||
* ( A ) - - - - - - - > | {A,B} {}|< - - -( B )
|
||||
* \___/ |_________| \___/
|
||||
* unlinked
|
||||
* | __ (nil)
|
||||
* | //| The following code causes this:
|
||||
* |__ // /\ _________
|
||||
* / \ // || | group Y | fn taskA() {
|
||||
* ( C )- - - ||- - - > |{C} {D,E}| spawn(taskB);
|
||||
* \___/ / \=====> |_________| spawn_unlinked(taskC);
|
||||
* supervise /gen \ ...
|
||||
* | __ \ 00 / }
|
||||
* | //| \__/ fn taskB() { ... }
|
||||
* |__ // /\ _________ fn taskC() {
|
||||
* / \/ || | group Z | spawn_supervised(taskD);
|
||||
* ( D )- - - ||- - - > | {D} {E} | ...
|
||||
* \___/ / \=====> |_________| }
|
||||
* supervise /gen \ fn taskD() {
|
||||
* | __ \ 01 / spawn_supervised(taskE);
|
||||
* | //| \__/ ...
|
||||
* |__ // _________ }
|
||||
* / \/ | group W | fn taskE() { ... }
|
||||
* ( E )- - - - - - - > | {E} {} |
|
||||
* \___/ |_________|
|
||||
*
|
||||
* "tcb" "taskgroup_arc"
|
||||
* "ancestor_list"
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#[doc(hidden)];
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use comm::Chan;
|
||||
use rt::local::Local;
|
||||
use rt::sched::{Scheduler, Shutdown, TaskFromFriend};
|
||||
use rt::task::{Task, Sched};
|
||||
use rt::thread::Thread;
|
||||
use rt::{in_green_task_context, new_event_loop};
|
||||
use task::{SingleThreaded, TaskOpts, TaskResult};
|
||||
|
||||
#[cfg(test)] use task::default_task_opts;
|
||||
#[cfg(test)] use task;
|
||||
|
||||
pub fn spawn_raw(mut opts: TaskOpts, f: proc()) {
|
||||
assert!(in_green_task_context());
|
||||
|
||||
let mut task = if opts.sched.mode != SingleThreaded {
|
||||
if opts.watched {
|
||||
Task::build_child(opts.stack_size, f)
|
||||
} else {
|
||||
Task::build_root(opts.stack_size, f)
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
// Creating a 1:1 task:thread ...
|
||||
let sched: *mut Scheduler = Local::unsafe_borrow();
|
||||
let sched_handle = (*sched).make_handle();
|
||||
|
||||
// Since this is a 1:1 scheduler we create a queue not in
|
||||
// the stealee set. The run_anything flag is set false
|
||||
// which will disable stealing.
|
||||
let (worker, _stealer) = (*sched).work_queue.pool().deque();
|
||||
|
||||
// Create a new scheduler to hold the new task
|
||||
let mut new_sched = ~Scheduler::new_special(new_event_loop(),
|
||||
worker,
|
||||
(*sched).work_queues.clone(),
|
||||
(*sched).sleeper_list.clone(),
|
||||
false,
|
||||
Some(sched_handle));
|
||||
let mut new_sched_handle = new_sched.make_handle();
|
||||
|
||||
// Allow the scheduler to exit when the pinned task exits
|
||||
new_sched_handle.send(Shutdown);
|
||||
|
||||
// Pin the new task to the new scheduler
|
||||
let new_task = if opts.watched {
|
||||
Task::build_homed_child(opts.stack_size, f, Sched(new_sched_handle))
|
||||
} else {
|
||||
Task::build_homed_root(opts.stack_size, f, Sched(new_sched_handle))
|
||||
};
|
||||
|
||||
// Create a task that will later be used to join with the new scheduler
|
||||
// thread when it is ready to terminate
|
||||
let (thread_port, thread_chan) = Chan::new();
|
||||
let join_task = do Task::build_child(None) {
|
||||
debug!("running join task");
|
||||
let thread: Thread<()> = thread_port.recv();
|
||||
thread.join();
|
||||
};
|
||||
|
||||
// Put the scheduler into another thread
|
||||
let orig_sched_handle = (*sched).make_handle();
|
||||
|
||||
let new_sched = new_sched;
|
||||
let thread = do Thread::start {
|
||||
let mut new_sched = new_sched;
|
||||
let mut orig_sched_handle = orig_sched_handle;
|
||||
|
||||
let bootstrap_task = ~do Task::new_root(&mut new_sched.stack_pool, None) || {
|
||||
debug!("boostrapping a 1:1 scheduler");
|
||||
};
|
||||
new_sched.bootstrap(bootstrap_task);
|
||||
|
||||
// Now tell the original scheduler to join with this thread
|
||||
// by scheduling a thread-joining task on the original scheduler
|
||||
orig_sched_handle.send(TaskFromFriend(join_task));
|
||||
|
||||
// NB: We can't simply send a message from here to another task
|
||||
// because this code isn't running in a task and message passing doesn't
|
||||
// work outside of tasks. Hence we're sending a scheduler message
|
||||
// to execute a new task directly to a scheduler.
|
||||
};
|
||||
|
||||
// Give the thread handle to the join task
|
||||
thread_chan.send(thread);
|
||||
|
||||
// When this task is enqueued on the current scheduler it will then get
|
||||
// forwarded to the scheduler to which it is pinned
|
||||
new_task
|
||||
}
|
||||
};
|
||||
|
||||
if opts.notify_chan.is_some() {
|
||||
let notify_chan = opts.notify_chan.take_unwrap();
|
||||
let on_exit: proc(TaskResult) = proc(task_result) {
|
||||
notify_chan.try_send(task_result);
|
||||
};
|
||||
task.death.on_exit = Some(on_exit);
|
||||
}
|
||||
|
||||
task.name = opts.name.take();
|
||||
debug!("spawn calling run_task");
|
||||
Scheduler::run_task(task);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_raw_simple() {
|
||||
let (po, ch) = Chan::new();
|
||||
do spawn_raw(default_task_opts()) {
|
||||
ch.send(());
|
||||
}
|
||||
po.recv();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_raw_unsupervise() {
|
||||
let opts = task::TaskOpts {
|
||||
watched: false,
|
||||
notify_chan: None,
|
||||
.. default_task_opts()
|
||||
};
|
||||
do spawn_raw(opts) {
|
||||
fail!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_raw_notify_success() {
|
||||
let (notify_po, notify_ch) = Chan::new();
|
||||
|
||||
let opts = task::TaskOpts {
|
||||
notify_chan: Some(notify_ch),
|
||||
.. default_task_opts()
|
||||
};
|
||||
do spawn_raw(opts) {
|
||||
}
|
||||
assert!(notify_po.recv().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spawn_raw_notify_failure() {
|
||||
// New bindings for these
|
||||
let (notify_po, notify_ch) = Chan::new();
|
||||
|
||||
let opts = task::TaskOpts {
|
||||
watched: false,
|
||||
notify_chan: Some(notify_ch),
|
||||
.. default_task_opts()
|
||||
};
|
||||
do spawn_raw(opts) {
|
||||
fail!();
|
||||
}
|
||||
assert!(notify_po.recv().is_err());
|
||||
}
|
|
@ -140,7 +140,6 @@ pub mod dl {
|
|||
use path;
|
||||
use ptr;
|
||||
use str;
|
||||
use unstable::sync::atomic;
|
||||
use result::*;
|
||||
|
||||
pub unsafe fn open_external(filename: &path::Path) -> *libc::c_void {
|
||||
|
@ -158,11 +157,7 @@ pub mod dl {
|
|||
static mut lock: Mutex = MUTEX_INIT;
|
||||
unsafe {
|
||||
// dlerror isn't thread safe, so we need to lock around this entire
|
||||
// sequence. `atomic` asserts that we don't do anything that
|
||||
// would cause this task to be descheduled, which could deadlock
|
||||
// the scheduler if it happens while the lock is held.
|
||||
// FIXME #9105 use a Rust mutex instead of C++ mutexes.
|
||||
let _guard = atomic();
|
||||
// sequence
|
||||
lock.lock();
|
||||
let _old_error = dlerror();
|
||||
|
||||
|
@ -208,7 +203,6 @@ pub mod dl {
|
|||
use libc;
|
||||
use path;
|
||||
use ptr;
|
||||
use unstable::sync::atomic;
|
||||
use result::*;
|
||||
|
||||
pub unsafe fn open_external(filename: &path::Path) -> *libc::c_void {
|
||||
|
@ -225,7 +219,6 @@ pub mod dl {
|
|||
|
||||
pub fn check_for_errors_in<T>(f: || -> T) -> Result<T, ~str> {
|
||||
unsafe {
|
||||
let _guard = atomic();
|
||||
SetLastError(0);
|
||||
|
||||
let result = f();
|
||||
|
|
|
@ -11,15 +11,13 @@
|
|||
//! Runtime calls emitted by the compiler.
|
||||
|
||||
use c_str::ToCStr;
|
||||
use cast::transmute;
|
||||
use libc::{c_char, size_t, uintptr_t};
|
||||
use rt::task;
|
||||
use rt::borrowck;
|
||||
|
||||
#[cold]
|
||||
#[lang="fail_"]
|
||||
pub fn fail_(expr: *c_char, file: *c_char, line: size_t) -> ! {
|
||||
task::begin_unwind_raw(expr, file, line);
|
||||
::rt::begin_unwind_raw(expr, file, line);
|
||||
}
|
||||
|
||||
#[cold]
|
||||
|
@ -81,15 +79,3 @@ pub unsafe fn check_not_borrowed(a: *u8,
|
|||
line: size_t) {
|
||||
borrowck::check_not_borrowed(a, file, line)
|
||||
}
|
||||
|
||||
#[lang="start"]
|
||||
pub fn start(main: *u8, argc: int, argv: **c_char) -> int {
|
||||
use rt;
|
||||
|
||||
unsafe {
|
||||
return do rt::start(argc, argv as **u8) {
|
||||
let main: extern "Rust" fn() = transmute(main);
|
||||
main();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue