Browse Source

refactor dependency resolution

Darien Raymond 7 years ago
parent
commit
a5dcb0f13e

+ 30 - 25
app/commander/commander.go

@@ -12,29 +12,46 @@ import (
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/signal/done"
+	"v2ray.com/core/features"
 	"v2ray.com/core/features/outbound"
 )
 
 // Commander is a V2Ray feature that provides gRPC methods to external clients.
 type Commander struct {
 	sync.Mutex
-	server *grpc.Server
-	config Config
-	v      *core.Instance
-	ohm    outbound.Manager
+	server   *grpc.Server
+	services []Service
+	ohm      outbound.Manager
+	tag      string
 }
 
 // NewCommander creates a new Commander based on the given config.
 func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
-	v := core.MustFromContext(ctx)
 	c := &Commander{
-		config: *config,
-		ohm:    v.OutboundHandlerManager(),
-		v:      v,
+		tag: config.Tag,
 	}
-	if err := v.RegisterFeature(c); err != nil {
-		return nil, err
+
+	v := core.MustFromContext(ctx)
+	v.RequireFeatures([]interface{}{outbound.ManagerType()}, func(fs []features.Feature) {
+		c.ohm = fs[0].(outbound.Manager)
+	})
+
+	for _, rawConfig := range config.Service {
+		config, err := rawConfig.GetInstance()
+		if err != nil {
+			return nil, err
+		}
+		rawService, err := common.CreateObject(ctx, config)
+		if err != nil {
+			return nil, err
+		}
+		service, ok := rawService.(Service)
+		if !ok {
+			return nil, newError("not a Service.")
+		}
+		c.services = append(c.services, service)
 	}
+
 	return c, nil
 }
 
@@ -47,19 +64,7 @@ func (c *Commander) Type() interface{} {
 func (c *Commander) Start() error {
 	c.Lock()
 	c.server = grpc.NewServer()
-	for _, rawConfig := range c.config.Service {
-		config, err := rawConfig.GetInstance()
-		if err != nil {
-			return err
-		}
-		rawService, err := core.CreateObject(c.v, config)
-		if err != nil {
-			return err
-		}
-		service, ok := rawService.(Service)
-		if !ok {
-			return newError("not a Service.")
-		}
+	for _, service := range c.services {
 		service.Register(c.server)
 	}
 	c.Unlock()
@@ -75,12 +80,12 @@ func (c *Commander) Start() error {
 		}
 	}()
 
-	if err := c.ohm.RemoveHandler(context.Background(), c.config.Tag); err != nil {
+	if err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil {
 		newError("failed to remove existing handler").WriteToLog()
 	}
 
 	return c.ohm.AddHandler(context.Background(), &Outbound{
-		tag:      c.config.Tag,
+		tag:      c.tag,
 		listener: listener,
 	})
 }

+ 15 - 9
app/dispatcher/default.go

@@ -16,6 +16,7 @@ import (
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/session"
 	"v2ray.com/core/common/vio"
+	"v2ray.com/core/features"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/routing"
@@ -91,17 +92,22 @@ type DefaultDispatcher struct {
 
 // NewDefaultDispatcher create a new DefaultDispatcher.
 func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
+	d := &DefaultDispatcher{}
+
 	v := core.MustFromContext(ctx)
-	d := &DefaultDispatcher{
-		ohm:    v.OutboundHandlerManager(),
-		router: v.Router(),
-		policy: v.PolicyManager(),
-		stats:  v.Stats(),
-	}
+	v.RequireFeatures([]interface{}{outbound.ManagerType(), routing.RouterType(), policy.ManagerType()}, func(fs []features.Feature) {
+		d.ohm = fs[0].(outbound.Manager)
+		d.router = fs[1].(routing.Router)
+		d.policy = fs[2].(policy.Manager)
+	})
+	v.RequireFeatures([]interface{}{core.ServerType()}, func([]features.Feature) {
+		f := v.GetFeature(stats.ManagerType())
+		if f == nil {
+			return
+		}
+		d.stats = f.(stats.Manager)
+	})
 
-	if err := v.RegisterFeature(d); err != nil {
-		return nil, newError("unable to register Dispatcher").Base(err)
-	}
 	return d, nil
 }
 

+ 8 - 4
app/dns/server.go

@@ -7,6 +7,8 @@ import (
 	"sync"
 	"time"
 
+	"v2ray.com/core/features/routing"
+
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
@@ -42,9 +44,6 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 	server.hosts = hosts
 
 	v := core.MustFromContext(ctx)
-	if err := v.RegisterFeature(server); err != nil {
-		return nil, newError("unable to register DNSClient.").Base(err)
-	}
 
 	addNameServer := func(endpoint *net.Endpoint) int {
 		address := endpoint.Address.AsAddress()
@@ -56,7 +55,12 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 				dest.Network = net.Network_UDP
 			}
 			if dest.Network == net.Network_UDP {
-				server.servers = append(server.servers, NewClassicNameServer(dest, v.Dispatcher(), server.clientIP))
+				idx := len(server.servers)
+				server.servers = append(server.servers, nil)
+				v.RequireFeatures([]interface{}{routing.DispatcherType()}, func(fs []features.Feature) {
+					dispatcher := fs[0].(routing.Dispatcher)
+					server.servers[idx] = NewClassicNameServer(dest, dispatcher, server.clientIP)
+				})
 			}
 		}
 		return len(server.servers) - 1

+ 3 - 2
app/dns/server_test.go

@@ -13,6 +13,7 @@ import (
 	_ "v2ray.com/core/app/proxyman/outbound"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
+	feature_dns "v2ray.com/core/features/dns"
 	"v2ray.com/core/proxy/freedom"
 	"v2ray.com/core/testing/servers/udp"
 	. "v2ray.com/ext/assert"
@@ -85,7 +86,7 @@ func TestUDPServer(t *testing.T) {
 	v, err := core.New(config)
 	assert(err, IsNil)
 
-	client := v.DNSClient()
+	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 	ips, err := client.LookupIP("google.com")
 	assert(err, IsNil)
@@ -171,7 +172,7 @@ func TestPrioritizedDomain(t *testing.T) {
 	v, err := core.New(config)
 	assert(err, IsNil)
 
-	client := v.DNSClient()
+	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 	startTime := time.Now()
 	ips, err := client.LookupIP("google.com")

+ 0 - 6
app/log/log.go

@@ -6,7 +6,6 @@ import (
 	"context"
 	"sync"
 
-	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/log"
 )
@@ -28,11 +27,6 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
 	}
 	log.RegisterHandler(g)
 
-	v := core.FromContext(ctx)
-	if v != nil {
-		common.Must(v.RegisterFeature(g))
-	}
-
 	return g, nil
 }
 

+ 0 - 8
app/policy/manager.go

@@ -3,7 +3,6 @@ package policy
 import (
 	"context"
 
-	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/features/policy"
 )
@@ -28,13 +27,6 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
 		}
 	}
 
-	v := core.FromContext(ctx)
-	if v != nil {
-		if err := v.RegisterFeature(m); err != nil {
-			return nil, newError("unable to register PolicyManager in core").Base(err).AtError()
-		}
-	}
-
 	return m, nil
 }
 

+ 9 - 4
app/proxyman/command/command.go

@@ -4,8 +4,10 @@ import (
 	"context"
 
 	grpc "google.golang.org/grpc"
+
 	"v2ray.com/core"
 	"v2ray.com/core/common"
+	"v2ray.com/core/features"
 	"v2ray.com/core/features/inbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/proxy"
@@ -136,11 +138,14 @@ type service struct {
 }
 
 func (s *service) Register(server *grpc.Server) {
-	RegisterHandlerServiceServer(server, &handlerServer{
-		s:   s.v,
-		ihm: s.v.InboundHandlerManager(),
-		ohm: s.v.OutboundHandlerManager(),
+	hs := &handlerServer{
+		s: s.v,
+	}
+	s.v.RequireFeatures([]interface{}{inbound.ManagerType(), outbound.ManagerType()}, func(fs []features.Feature) {
+		hs.ihm = fs[0].(inbound.Manager)
+		hs.ohm = fs[1].(outbound.Manager)
 	})
+	RegisterHandlerServiceServer(server, hs)
 }
 
 func init() {

+ 4 - 2
app/proxyman/inbound/always.go

@@ -10,6 +10,7 @@ import (
 	"v2ray.com/core/common/dice"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
+	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/stats"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
@@ -19,9 +20,9 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
 	var uplinkCounter stats.Counter
 	var downlinkCounter stats.Counter
 
-	policy := v.PolicyManager()
-	statsManager := v.Stats()
+	policy := v.GetFeature(policy.ManagerType()).(policy.Manager)
 	if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
+		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
 		name := "inbound>>>" + tag + ">>>traffic>>>uplink"
 		c, _ := stats.GetOrRegisterCounter(statsManager, name)
 		if c != nil {
@@ -29,6 +30,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
 		}
 	}
 	if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
+		statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
 		name := "inbound>>>" + tag + ">>>traffic>>>downlink"
 		c, _ := stats.GetOrRegisterCounter(statsManager, name)
 		if c != nil {

+ 0 - 4
app/proxyman/inbound/inbound.go

@@ -27,10 +27,6 @@ func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error)
 	m := &Manager{
 		taggedHandlers: make(map[string]inbound.Handler),
 	}
-	v := core.MustFromContext(ctx)
-	if err := v.RegisterFeature(m); err != nil {
-		return nil, newError("unable to register InboundHandlerManager").Base(err)
-	}
 	return m, nil
 }
 

+ 7 - 3
app/proxyman/mux/mux.go

@@ -8,6 +8,8 @@ import (
 	"sync"
 	"time"
 
+	"v2ray.com/core/features"
+
 	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
@@ -304,9 +306,11 @@ type Server struct {
 
 // NewServer creates a new mux.Server.
 func NewServer(ctx context.Context) *Server {
-	s := &Server{
-		dispatcher: core.MustFromContext(ctx).Dispatcher(),
-	}
+	s := &Server{}
+	v := core.MustFromContext(ctx)
+	v.RequireFeatures([]interface{}{routing.DispatcherType()}, func(fs []features.Feature) {
+		s.dispatcher = fs[0].(routing.Dispatcher)
+	})
 	return s
 }
 

+ 1 - 1
app/proxyman/outbound/handler.go

@@ -29,7 +29,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
 	v := core.MustFromContext(ctx)
 	h := &Handler{
 		config:          config,
-		outboundManager: v.OutboundHandlerManager(),
+		outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
 	}
 
 	if config.SenderSettings != nil {

+ 0 - 4
app/proxyman/outbound/outbound.go

@@ -26,10 +26,6 @@ func New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error)
 	m := &Manager{
 		taggedHandler: make(map[string]outbound.Handler),
 	}
-	v := core.MustFromContext(ctx)
-	if err := v.RegisterFeature(m); err != nil {
-		return nil, newError("unable to register outbound.Manager").Base(err)
-	}
 	return m, nil
 }
 

+ 5 - 5
app/router/router.go

@@ -9,6 +9,7 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/session"
+	"v2ray.com/core/features"
 	"v2ray.com/core/features/dns"
 	"v2ray.com/core/features/routing"
 	"v2ray.com/core/proxy"
@@ -23,11 +24,9 @@ type Router struct {
 
 // NewRouter creates a new Router based on the given config.
 func NewRouter(ctx context.Context, config *Config) (*Router, error) {
-	v := core.MustFromContext(ctx)
 	r := &Router{
 		domainStrategy: config.DomainStrategy,
 		rules:          make([]Rule, len(config.Rule)),
-		dns:            v.DNSClient(),
 	}
 
 	for idx, rule := range config.Rule {
@@ -39,9 +38,10 @@ func NewRouter(ctx context.Context, config *Config) (*Router, error) {
 		r.rules[idx].Condition = cond
 	}
 
-	if err := v.RegisterFeature(r); err != nil {
-		return nil, newError("unable to register Router").Base(err)
-	}
+	v := core.MustFromContext(ctx)
+	v.RequireFeatures([]interface{}{dns.ClientType()}, func(fs []features.Feature) {
+		r.dns = fs[0].(dns.Client)
+	})
 	return r, nil
 }
 

+ 2 - 1
app/router/router_test.go

@@ -12,6 +12,7 @@ import (
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/session"
+	"v2ray.com/core/features/routing"
 	. "v2ray.com/ext/assert"
 )
 
@@ -38,7 +39,7 @@ func TestSimpleRouter(t *testing.T) {
 	v, err := core.New(config)
 	common.Must(err)
 
-	r := v.Router()
+	r := v.GetFeature(routing.RouterType()).(routing.Router)
 
 	ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)})
 	tag, err := r.PickRoute(ctx)

+ 2 - 1
app/stats/command/command.go

@@ -79,7 +79,8 @@ type service struct {
 }
 
 func (s *service) Register(server *grpc.Server) {
-	RegisterStatsServiceServer(server, NewStatsServer(s.v.Stats()))
+	f := s.v.GetFeature(feature_stats.ManagerType())
+	RegisterStatsServiceServer(server, NewStatsServer(f.(feature_stats.Manager)))
 }
 
 func init() {

+ 0 - 8
app/stats/stats.go

@@ -7,7 +7,6 @@ import (
 	"sync"
 	"sync/atomic"
 
-	"v2ray.com/core"
 	"v2ray.com/core/features/stats"
 )
 
@@ -42,13 +41,6 @@ func NewManager(ctx context.Context, config *Config) (*Manager, error) {
 		counters: make(map[string]*Counter),
 	}
 
-	v := core.FromContext(ctx)
-	if v != nil {
-		if err := v.RegisterFeature(m); err != nil {
-			return nil, newError("failed to register StatManager").Base(err)
-		}
-	}
-
 	return m, nil
 }
 

+ 0 - 59
dns.go

@@ -1,59 +0,0 @@
-package core
-
-import (
-	"sync"
-
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/features/dns"
-)
-
-type syncDNSClient struct {
-	sync.RWMutex
-	dns.Client
-}
-
-func (d *syncDNSClient) Type() interface{} {
-	return dns.ClientType()
-}
-
-func (d *syncDNSClient) LookupIP(host string) ([]net.IP, error) {
-	d.RLock()
-	defer d.RUnlock()
-
-	if d.Client == nil {
-		return net.LookupIP(host)
-	}
-
-	return d.Client.LookupIP(host)
-}
-
-func (d *syncDNSClient) Start() error {
-	d.RLock()
-	defer d.RUnlock()
-
-	if d.Client == nil {
-		return nil
-	}
-
-	return d.Client.Start()
-}
-
-func (d *syncDNSClient) Close() error {
-	d.RLock()
-	defer d.RUnlock()
-
-	return common.Close(d.Client)
-}
-
-func (d *syncDNSClient) Set(client dns.Client) {
-	if client == nil {
-		return
-	}
-
-	d.Lock()
-	defer d.Unlock()
-
-	common.Close(d.Client) // nolint: errcheck
-	d.Client = client
-}

+ 13 - 0
features/dns/client.go

@@ -15,3 +15,16 @@ type Client interface {
 func ClientType() interface{} {
 	return (*Client)(nil)
 }
+
+type LocalClient struct{}
+
+func (LocalClient) Type() interface{} {
+	return ClientType()
+}
+
+func (LocalClient) Start() error { return nil }
+func (LocalClient) Close() error { return nil }
+
+func (LocalClient) LookupIP(host string) ([]net.IP, error) {
+	return net.LookupIP(host)
+}

+ 31 - 0
features/policy/default.go

@@ -0,0 +1,31 @@
+package policy
+
+import (
+	"time"
+)
+
+type DefaultManager struct{}
+
+func (DefaultManager) Type() interface{} {
+	return ManagerType()
+}
+
+func (DefaultManager) ForLevel(level uint32) Session {
+	p := SessionDefault()
+	if level == 1 {
+		p.Timeouts.ConnectionIdle = time.Second * 600
+	}
+	return p
+}
+
+func (DefaultManager) ForSystem() System {
+	return System{}
+}
+
+func (DefaultManager) Start() error {
+	return nil
+}
+
+func (DefaultManager) Close() error {
+	return nil
+}

+ 19 - 0
features/routing/router.go

@@ -3,6 +3,7 @@ package routing
 import (
 	"context"
 
+	"v2ray.com/core/common"
 	"v2ray.com/core/features"
 )
 
@@ -18,3 +19,21 @@ type Router interface {
 func RouterType() interface{} {
 	return (*Router)(nil)
 }
+
+type DefaultRouter struct{}
+
+func (DefaultRouter) Type() interface{} {
+	return RouterType()
+}
+
+func (DefaultRouter) PickRoute(ctx context.Context) (string, error) {
+	return "", common.ErrNoClue
+}
+
+func (DefaultRouter) Start() error {
+	return nil
+}
+
+func (DefaultRouter) Close() error {
+	return nil
+}

+ 6 - 1
functions.go

@@ -6,6 +6,7 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
+	"v2ray.com/core/features/routing"
 )
 
 // CreateObject creates a new object based on the given V2Ray instance and config. The V2Ray instance may be nil.
@@ -41,7 +42,11 @@ func StartInstance(configFormat string, configBytes []byte) (*Instance, error) {
 // Since it is under a proxy context, the LocalAddr() and RemoteAddr() in returned net.Conn
 // will not show real addresses being used for communication.
 func Dial(ctx context.Context, v *Instance, dest net.Destination) (net.Conn, error) {
-	r, err := v.Dispatcher().Dispatch(ctx, dest)
+	dispatcher := v.GetFeature(routing.DispatcherType())
+	if dispatcher == nil {
+		return nil, newError("routing.Dispatcher is not registered in V2Ray core")
+	}
+	r, err := dispatcher.(routing.Dispatcher).Dispatch(ctx, dest)
 	if err != nil {
 		return nil, err
 	}

+ 0 - 143
network.go

@@ -1,143 +0,0 @@
-package core
-
-import (
-	"context"
-	"sync"
-
-	"v2ray.com/core/common"
-	"v2ray.com/core/features/inbound"
-	"v2ray.com/core/features/outbound"
-)
-
-type syncInboundHandlerManager struct {
-	sync.RWMutex
-	inbound.Manager
-}
-
-func (*syncInboundHandlerManager) Type() interface{} {
-	return inbound.ManagerType()
-}
-
-func (m *syncInboundHandlerManager) GetHandler(ctx context.Context, tag string) (inbound.Handler, error) {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return nil, newError("inbound.Manager not set.").AtError()
-	}
-
-	return m.Manager.GetHandler(ctx, tag)
-}
-
-func (m *syncInboundHandlerManager) AddHandler(ctx context.Context, handler inbound.Handler) error {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return newError("inbound.Manager not set.").AtError()
-	}
-
-	return m.Manager.AddHandler(ctx, handler)
-}
-
-func (m *syncInboundHandlerManager) Start() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return newError("inbound.Manager not set.").AtError()
-	}
-
-	return m.Manager.Start()
-}
-
-func (m *syncInboundHandlerManager) Close() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	return common.Close(m.Manager)
-}
-
-func (m *syncInboundHandlerManager) Set(manager inbound.Manager) {
-	if manager == nil {
-		return
-	}
-
-	m.Lock()
-	defer m.Unlock()
-
-	common.Close(m.Manager) // nolint: errcheck
-	m.Manager = manager
-}
-
-type syncOutboundHandlerManager struct {
-	sync.RWMutex
-	outbound.Manager
-}
-
-func (*syncOutboundHandlerManager) Type() interface{} {
-	return outbound.ManagerType()
-}
-
-func (m *syncOutboundHandlerManager) GetHandler(tag string) outbound.Handler {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return nil
-	}
-
-	return m.Manager.GetHandler(tag)
-}
-
-func (m *syncOutboundHandlerManager) GetDefaultHandler() outbound.Handler {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return nil
-	}
-
-	return m.Manager.GetDefaultHandler()
-}
-
-func (m *syncOutboundHandlerManager) AddHandler(ctx context.Context, handler outbound.Handler) error {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return newError("OutboundHandlerManager not set.").AtError()
-	}
-
-	return m.Manager.AddHandler(ctx, handler)
-}
-
-func (m *syncOutboundHandlerManager) Start() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return newError("OutboundHandlerManager not set.").AtError()
-	}
-
-	return m.Manager.Start()
-}
-
-func (m *syncOutboundHandlerManager) Close() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	return common.Close(m.Manager)
-}
-
-func (m *syncOutboundHandlerManager) Set(manager outbound.Manager) {
-	if manager == nil {
-		return
-	}
-
-	m.Lock()
-	defer m.Unlock()
-
-	common.Close(m.Manager) // nolint: errcheck
-	m.Manager = manager
-}

+ 0 - 74
policy.go

@@ -1,74 +0,0 @@
-package core
-
-import (
-	"sync"
-	"time"
-
-	"v2ray.com/core/common"
-	"v2ray.com/core/features/policy"
-)
-
-type syncPolicyManager struct {
-	sync.RWMutex
-	policy.Manager
-}
-
-func (*syncPolicyManager) Type() interface{} {
-	return policy.ManagerType()
-}
-
-func (m *syncPolicyManager) ForLevel(level uint32) policy.Session {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		p := policy.SessionDefault()
-		if level == 1 {
-			p.Timeouts.ConnectionIdle = time.Second * 600
-		}
-		return p
-	}
-
-	return m.Manager.ForLevel(level)
-}
-
-func (m *syncPolicyManager) ForSystem() policy.System {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return policy.System{}
-	}
-
-	return m.Manager.ForSystem()
-}
-
-func (m *syncPolicyManager) Start() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	if m.Manager == nil {
-		return nil
-	}
-
-	return m.Manager.Start()
-}
-
-func (m *syncPolicyManager) Close() error {
-	m.RLock()
-	defer m.RUnlock()
-
-	return common.Close(m.Manager)
-}
-
-func (m *syncPolicyManager) Set(manager policy.Manager) {
-	if manager == nil {
-		return
-	}
-
-	m.Lock()
-	defer m.Unlock()
-
-	common.Close(m.Manager) // nolint: errcheck
-	m.Manager = manager
-}

+ 1 - 1
proxy/dokodemo/dokodemo.go

@@ -35,7 +35,7 @@ func New(ctx context.Context, config *Config) (*DokodemoDoor, error) {
 		config:        config,
 		address:       config.GetPredefinedAddress(),
 		port:          net.Port(config.Port),
-		policyManager: v.PolicyManager(),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 
 	return d, nil

+ 9 - 4
proxy/freedom/freedom.go

@@ -6,6 +6,8 @@ import (
 	"context"
 	"time"
 
+	"v2ray.com/core/features"
+
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
@@ -31,13 +33,16 @@ type Handler struct {
 
 // New creates a new Freedom handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	v := core.MustFromContext(ctx)
 	f := &Handler{
-		config:        *config,
-		policyManager: v.PolicyManager(),
-		dns:           v.DNSClient(),
+		config: *config,
 	}
 
+	v := core.MustFromContext(ctx)
+	v.RequireFeatures([]interface{}{policy.ManagerType(), dns.ClientType()}, func(fs []features.Feature) {
+		f.policyManager = fs[0].(policy.Manager)
+		f.dns = fs[1].(dns.Client)
+	})
+
 	return f, nil
 }
 

+ 6 - 5
proxy/http/server.go

@@ -28,15 +28,16 @@ import (
 
 // Server is an HTTP proxy server.
 type Server struct {
-	config *ServerConfig
-	v      *core.Instance
+	config        *ServerConfig
+	policyManager policy.Manager
 }
 
 // NewServer creates a new HTTP inbound handler.
 func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
+	v := core.MustFromContext(ctx)
 	s := &Server{
-		config: config,
-		v:      core.MustFromContext(ctx),
+		config:        config,
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 
 	return s, nil
@@ -44,7 +45,7 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 
 func (s *Server) policy() policy.Session {
 	config := s.config
-	p := s.v.PolicyManager().ForLevel(config.UserLevel)
+	p := s.policyManager.ForLevel(config.UserLevel)
 	if config.Timeout > 0 && config.UserLevel == 0 {
 		p.Timeouts.ConnectionIdle = time.Duration(config.Timeout) * time.Second
 	}

+ 1 - 1
proxy/mtproto/server.go

@@ -56,7 +56,7 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	return &Server{
 		user:    user,
 		account: account,
-		policy:  v.PolicyManager(),
+		policy:  v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}, nil
 }
 

+ 11 - 9
proxy/shadowsocks/client.go

@@ -3,25 +3,25 @@ package shadowsocks
 import (
 	"context"
 
-	"v2ray.com/core/common/session"
-	"v2ray.com/core/common/task"
-	"v2ray.com/core/common/vio"
-
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/retry"
+	"v2ray.com/core/common/session"
 	"v2ray.com/core/common/signal"
+	"v2ray.com/core/common/task"
+	"v2ray.com/core/common/vio"
+	"v2ray.com/core/features/policy"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/transport/internet"
 )
 
 // Client is a inbound handler for Shadowsocks protocol
 type Client struct {
-	serverPicker protocol.ServerPicker
-	v            *core.Instance
+	serverPicker  protocol.ServerPicker
+	policyManager policy.Manager
 }
 
 // NewClient create a new Shadowsocks client.
@@ -37,9 +37,11 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
 	if serverList.Size() == 0 {
 		return nil, newError("0 server")
 	}
+
+	v := core.MustFromContext(ctx)
 	client := &Client{
-		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
-		v:            core.MustFromContext(ctx),
+		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 	return client, nil
 }
@@ -97,7 +99,7 @@ func (c *Client) Process(ctx context.Context, link *vio.Link, dialer proxy.Diale
 		request.Option |= RequestOptionOneTimeAuth
 	}
 
-	sessionPolicy := c.v.PolicyManager().ForLevel(user.Level)
+	sessionPolicy := c.policyManager.ForLevel(user.Level)
 	ctx, cancel := context.WithCancel(ctx)
 	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
 

+ 8 - 7
proxy/shadowsocks/server.go

@@ -21,9 +21,9 @@ import (
 )
 
 type Server struct {
-	config ServerConfig
-	user   *protocol.MemoryUser
-	v      *core.Instance
+	config        ServerConfig
+	user          *protocol.MemoryUser
+	policyManager policy.Manager
 }
 
 // NewServer create a new Shadowsocks server.
@@ -37,10 +37,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 		return nil, newError("failed to parse user account").Base(err)
 	}
 
+	v := core.MustFromContext(ctx)
 	s := &Server{
-		config: *config,
-		user:   mUser,
-		v:      core.MustFromContext(ctx),
+		config:        *config,
+		user:          mUser,
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 
 	return s, nil
@@ -150,7 +151,7 @@ func (s *Server) handlerUDPPayload(ctx context.Context, conn internet.Connection
 }
 
 func (s *Server) handleConnection(ctx context.Context, conn internet.Connection, dispatcher routing.Dispatcher) error {
-	sessionPolicy := s.v.PolicyManager().ForLevel(s.user.Level)
+	sessionPolicy := s.policyManager.ForLevel(s.user.Level)
 	conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake))
 
 	bufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)}

+ 1 - 1
proxy/socks/client.go

@@ -43,7 +43,7 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
 	v := core.MustFromContext(ctx)
 	return &Client{
 		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
-		policyManager: v.PolicyManager(),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}, nil
 }
 

+ 1 - 1
proxy/socks/server.go

@@ -33,7 +33,7 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 	v := core.MustFromContext(ctx)
 	s := &Server{
 		config:        config,
-		policyManager: v.PolicyManager(),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 	return s, nil
 }

+ 2 - 2
proxy/vmess/inbound/inbound.go

@@ -114,8 +114,8 @@ type Handler struct {
 func New(ctx context.Context, config *Config) (*Handler, error) {
 	v := core.MustFromContext(ctx)
 	handler := &Handler{
-		policyManager:         v.PolicyManager(),
-		inboundHandlerManager: v.InboundHandlerManager(),
+		policyManager:         v.GetFeature(policy.ManagerType()).(policy.Manager),
+		inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager),
 		clients:               vmess.NewTimedUserValidator(protocol.DefaultIDHash),
 		detours:               config.Detour,
 		usersByEmail:          newUserByEmail(config.GetDefaultValue()),

+ 10 - 7
proxy/vmess/outbound/outbound.go

@@ -17,6 +17,7 @@ import (
 	"v2ray.com/core/common/signal"
 	"v2ray.com/core/common/task"
 	"v2ray.com/core/common/vio"
+	"v2ray.com/core/features/policy"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy/vmess"
 	"v2ray.com/core/proxy/vmess/encoding"
@@ -25,9 +26,9 @@ import (
 
 // Handler is an outbound connection handler for VMess protocol.
 type Handler struct {
-	serverList   *protocol.ServerList
-	serverPicker protocol.ServerPicker
-	v            *core.Instance
+	serverList    *protocol.ServerList
+	serverPicker  protocol.ServerPicker
+	policyManager policy.Manager
 }
 
 // New creates a new VMess outbound handler.
@@ -40,10 +41,12 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 		}
 		serverList.AddServer(s)
 	}
+
+	v := core.MustFromContext(ctx)
 	handler := &Handler{
-		serverList:   serverList,
-		serverPicker: protocol.NewRoundRobinServerPicker(serverList),
-		v:            core.MustFromContext(ctx),
+		serverList:    serverList,
+		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 
 	return handler, nil
@@ -109,7 +112,7 @@ func (v *Handler) Process(ctx context.Context, link *vio.Link, dialer proxy.Dial
 	output := link.Writer
 
 	session := encoding.NewClientSession(protocol.DefaultIDHash)
-	sessionPolicy := v.v.PolicyManager().ForLevel(request.User.Level)
+	sessionPolicy := v.policyManager.ForLevel(request.User.Level)
 
 	ctx, cancel := context.WithCancel(ctx)
 	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)

+ 0 - 111
router.go

@@ -1,111 +0,0 @@
-package core
-
-import (
-	"context"
-	"sync"
-
-	"v2ray.com/core/common"
-	"v2ray.com/core/common/net"
-	"v2ray.com/core/common/vio"
-	"v2ray.com/core/features/routing"
-)
-
-type syncDispatcher struct {
-	sync.RWMutex
-	routing.Dispatcher
-}
-
-func (*syncDispatcher) Type() interface{} {
-	return routing.DispatcherType()
-}
-
-func (d *syncDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*vio.Link, error) {
-	d.RLock()
-	defer d.RUnlock()
-
-	if d.Dispatcher == nil {
-		return nil, newError("Dispatcher not set.").AtError()
-	}
-
-	return d.Dispatcher.Dispatch(ctx, dest)
-}
-
-func (d *syncDispatcher) Start() error {
-	d.RLock()
-	defer d.RUnlock()
-
-	if d.Dispatcher == nil {
-		return newError("Dispatcher not set.").AtError()
-	}
-
-	return d.Dispatcher.Start()
-}
-
-func (d *syncDispatcher) Close() error {
-	d.RLock()
-	defer d.RUnlock()
-
-	return common.Close(d.Dispatcher)
-}
-
-func (d *syncDispatcher) Set(disp routing.Dispatcher) {
-	if disp == nil {
-		return
-	}
-
-	d.Lock()
-	defer d.Unlock()
-
-	common.Close(d.Dispatcher) // nolint: errcheck
-	d.Dispatcher = disp
-}
-
-type syncRouter struct {
-	sync.RWMutex
-	routing.Router
-}
-
-func (*syncRouter) Type() interface{} {
-	return routing.RouterType()
-}
-
-func (r *syncRouter) PickRoute(ctx context.Context) (string, error) {
-	r.RLock()
-	defer r.RUnlock()
-
-	if r.Router == nil {
-		return "", common.ErrNoClue
-	}
-
-	return r.Router.PickRoute(ctx)
-}
-
-func (r *syncRouter) Start() error {
-	r.RLock()
-	defer r.RUnlock()
-
-	if r.Router == nil {
-		return nil
-	}
-
-	return r.Router.Start()
-}
-
-func (r *syncRouter) Close() error {
-	r.RLock()
-	defer r.RUnlock()
-
-	return common.Close(r.Router)
-}
-
-func (r *syncRouter) Set(router routing.Router) {
-	if router == nil {
-		return
-	}
-
-	r.Lock()
-	defer r.Unlock()
-
-	common.Close(r.Router) // nolint: errcheck
-	r.Router = router
-}

+ 0 - 70
stats.go

@@ -1,70 +0,0 @@
-package core
-
-import (
-	"sync"
-
-	"v2ray.com/core/features/stats"
-)
-
-type syncStatManager struct {
-	sync.RWMutex
-	stats.Manager
-}
-
-func (*syncStatManager) Type() interface{} {
-	return stats.ManagerType()
-}
-
-func (s *syncStatManager) Start() error {
-	s.RLock()
-	defer s.RUnlock()
-
-	if s.Manager == nil {
-		return nil
-	}
-
-	return s.Manager.Start()
-}
-
-func (s *syncStatManager) Close() error {
-	s.RLock()
-	defer s.RUnlock()
-
-	if s.Manager == nil {
-		return nil
-	}
-	return s.Manager.Close()
-}
-
-func (s *syncStatManager) RegisterCounter(name string) (stats.Counter, error) {
-	s.RLock()
-	defer s.RUnlock()
-
-	if s.Manager == nil {
-		return nil, newError("StatManager not set.")
-	}
-	return s.Manager.RegisterCounter(name)
-}
-
-func (s *syncStatManager) GetCounter(name string) stats.Counter {
-	s.RLock()
-	defer s.RUnlock()
-
-	if s.Manager == nil {
-		return nil
-	}
-	return s.Manager.GetCounter(name)
-}
-
-func (s *syncStatManager) Set(m stats.Manager) {
-	if m == nil {
-		return
-	}
-	s.Lock()
-	defer s.Unlock()
-
-	if s.Manager != nil {
-		s.Manager.Close() // nolint: errcheck
-	}
-	s.Manager = m
-}

+ 148 - 144
v2ray.go

@@ -2,49 +2,71 @@ package core
 
 import (
 	"context"
+	fmt "fmt"
+	"reflect"
 	"sync"
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/serial"
-	"v2ray.com/core/common/uuid"
 	"v2ray.com/core/features"
 	"v2ray.com/core/features/dns"
 	"v2ray.com/core/features/inbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/routing"
-	"v2ray.com/core/features/stats"
 )
 
 // Server is an instance of V2Ray. At any time, there must be at most one Server instance running.
-// Deprecated. Use Instance directly.
 type Server interface {
 	common.Runnable
 }
 
+// ServerType returns the type of the server.
+func ServerType() interface{} {
+	return (*Instance)(nil)
+}
+
+type resolution struct {
+	deps     []interface{}
+	callback func([]features.Feature)
+}
+
+func getFeature(allFeatures []features.Feature, t interface{}) features.Feature {
+	for _, f := range allFeatures {
+		if f.Type() == t {
+			return f
+		}
+	}
+	return nil
+}
+
+func (r *resolution) resolve(allFeatures []features.Feature) bool {
+	var fs []features.Feature
+	for _, d := range r.deps {
+		f := getFeature(allFeatures, d)
+		if f == nil {
+			return false
+		}
+		fs = append(fs, f)
+	}
+
+	r.callback(fs)
+	return true
+}
+
 // Instance combines all functionalities in V2Ray.
 type Instance struct {
-	dnsClient     syncDNSClient
-	policyManager syncPolicyManager
-	dispatcher    syncDispatcher
-	router        syncRouter
-	ihm           syncInboundHandlerManager
-	ohm           syncOutboundHandlerManager
-	stats         syncStatManager
-
-	access   sync.Mutex
-	features []features.Feature
-	id       uuid.UUID
-	running  bool
+	access             sync.Mutex
+	features           []features.Feature
+	featureResolutions []resolution
+	running            bool
 }
 
 // New returns a new V2Ray instance based on given configuration.
 // The instance is not started at this point.
 // To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.
 func New(config *Config) (*Instance, error) {
-	var server = &Instance{
-		id: uuid.New(),
-	}
+	var server = &Instance{}
 
 	if config.Transport != nil {
 		features.PrintDeprecatedFeatureWarning("global transport settings")
@@ -58,45 +80,83 @@ func New(config *Config) (*Instance, error) {
 		if err != nil {
 			return nil, err
 		}
-		if _, err := CreateObject(server, settings); err != nil {
+		obj, err := CreateObject(server, settings)
+		if err != nil {
 			return nil, err
 		}
+		if feature, ok := obj.(features.Feature); ok {
+			server.AddFeature(feature)
+		}
 	}
 
-	for _, inboundConfig := range config.Inbound {
-		rawHandler, err := CreateObject(server, inboundConfig)
-		if err != nil {
-			return nil, err
-		}
-		handler, ok := rawHandler.(inbound.Handler)
-		if !ok {
-			return nil, newError("not an InboundHandler")
+	if server.GetFeature(dns.ClientType()) == nil {
+		server.AddFeature(dns.LocalClient{})
+	}
+
+	if server.GetFeature(policy.ManagerType()) == nil {
+		server.AddFeature(policy.DefaultManager{})
+	}
+
+	if server.GetFeature(routing.RouterType()) == nil {
+		server.AddFeature(routing.DefaultRouter{})
+	}
+
+	server.AddFeature(&Instance{})
+
+	if server.featureResolutions != nil {
+		fmt.Println("registered")
+		for _, d := range server.features {
+			fmt.Println(reflect.TypeOf(d.Type()))
 		}
-		if err := server.InboundHandlerManager().AddHandler(context.Background(), handler); err != nil {
-			return nil, err
+		for idx, r := range server.featureResolutions {
+			fmt.Println(idx)
+			for _, d := range r.deps {
+				fmt.Println(reflect.TypeOf(d))
+			}
 		}
+		return nil, newError("not all dependency are resolved.")
 	}
 
-	for _, outboundConfig := range config.Outbound {
-		rawHandler, err := CreateObject(server, outboundConfig)
-		if err != nil {
-			return nil, err
-		}
-		handler, ok := rawHandler.(outbound.Handler)
-		if !ok {
-			return nil, newError("not an OutboundHandler")
+	if len(config.Inbound) > 0 {
+		inboundManager := server.GetFeature(inbound.ManagerType()).(inbound.Manager)
+		for _, inboundConfig := range config.Inbound {
+			rawHandler, err := CreateObject(server, inboundConfig)
+			if err != nil {
+				return nil, err
+			}
+			handler, ok := rawHandler.(inbound.Handler)
+			if !ok {
+				return nil, newError("not an InboundHandler")
+			}
+			if err := inboundManager.AddHandler(context.Background(), handler); err != nil {
+				return nil, err
+			}
 		}
-		if err := server.OutboundHandlerManager().AddHandler(context.Background(), handler); err != nil {
-			return nil, err
+	}
+
+	if len(config.Outbound) > 0 {
+		outboundManager := server.GetFeature(outbound.ManagerType()).(outbound.Manager)
+		for _, outboundConfig := range config.Outbound {
+			rawHandler, err := CreateObject(server, outboundConfig)
+			if err != nil {
+				return nil, err
+			}
+			handler, ok := rawHandler.(outbound.Handler)
+			if !ok {
+				return nil, newError("not an OutboundHandler")
+			}
+			if err := outboundManager.AddHandler(context.Background(), handler); err != nil {
+				return nil, err
+			}
 		}
 	}
 
 	return server, nil
 }
 
-// ID returns a unique ID for this V2Ray instance.
-func (s *Instance) ID() uuid.UUID {
-	return s.id
+// Type implements common.HasType.
+func (s *Instance) Type() interface{} {
+	return ServerType()
 }
 
 // Close shutdown the V2Ray instance.
@@ -107,7 +167,7 @@ func (s *Instance) Close() error {
 	s.running = false
 
 	var errors []interface{}
-	for _, f := range s.allFeatures() {
+	for _, f := range s.features {
 		if err := f.Close(); err != nil {
 			errors = append(errors, err)
 		}
@@ -119,121 +179,65 @@ func (s *Instance) Close() error {
 	return nil
 }
 
-// Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.
-// A V2Ray instance can be started only once. Upon closing, the instance is not guaranteed to start again.
-func (s *Instance) Start() error {
-	s.access.Lock()
-	defer s.access.Unlock()
-
-	s.running = true
-	for _, f := range s.allFeatures() {
-		if err := f.Start(); err != nil {
-			return err
-		}
+// RequireFeatures registers a callback, which will be called when all dependent features are registered.
+func (s *Instance) RequireFeatures(featureTypes []interface{}, callback func([]features.Feature)) {
+	r := resolution{
+		deps:     featureTypes,
+		callback: callback,
 	}
-
-	newError("V2Ray ", Version(), " started").AtWarning().WriteToLog()
-
-	return nil
-}
-
-// RegisterFeature registers the given feature into V2Ray.
-// If feature is one of the following types, the corresponding feature in this Instance
-// will be replaced: DNSClient, PolicyManager, Router, Dispatcher, InboundHandlerManager, OutboundHandlerManager.
-func (s *Instance) RegisterFeature(instance features.Feature) error {
-	running := false
-
-	switch instance.Type().(type) {
-	case dns.Client, *dns.Client:
-		s.dnsClient.Set(instance.(dns.Client))
-	case policy.Manager, *policy.Manager:
-		s.policyManager.Set(instance.(policy.Manager))
-	case routing.Router, *routing.Router:
-		s.router.Set(instance.(routing.Router))
-	case routing.Dispatcher, *routing.Dispatcher:
-		s.dispatcher.Set(instance.(routing.Dispatcher))
-	case inbound.Manager, *inbound.Manager:
-		s.ihm.Set(instance.(inbound.Manager))
-	case outbound.Manager, *outbound.Manager:
-		s.ohm.Set(instance.(outbound.Manager))
-	case stats.Manager, *stats.Manager:
-		s.stats.Set(instance.(stats.Manager))
-	default:
-		s.access.Lock()
-		s.features = append(s.features, instance)
-		running = s.running
-		s.access.Unlock()
-	}
-
-	if running {
-		return instance.Start()
+	if r.resolve(s.features) {
+		return
 	}
-	return nil
+	s.featureResolutions = append(s.featureResolutions, r)
 }
 
-func (s *Instance) allFeatures() []features.Feature {
-	return append([]features.Feature{s.DNSClient(), s.PolicyManager(), s.Dispatcher(), s.Router(), s.InboundHandlerManager(), s.OutboundHandlerManager(), s.Stats()}, s.features...)
-}
+// AddFeature registers a feature into current Instance.
+func (s *Instance) AddFeature(feature features.Feature) {
+	s.features = append(s.features, feature)
 
-// GetFeature returns a feature that was registered in this Instance. Nil if not found.
-// The returned Feature must implement common.HasType and whose type equals to the given feature type.
-func (s *Instance) GetFeature(featureType interface{}) features.Feature {
-	switch featureType.(type) {
-	case dns.Client, *dns.Client:
-		return s.DNSClient()
-	case policy.Manager, *policy.Manager:
-		return s.PolicyManager()
-	case routing.Router, *routing.Router:
-		return s.Router()
-	case routing.Dispatcher, *routing.Dispatcher:
-		return s.Dispatcher()
-	case inbound.Manager, *inbound.Manager:
-		return s.InboundHandlerManager()
-	case outbound.Manager, *outbound.Manager:
-		return s.OutboundHandlerManager()
-	case stats.Manager, *stats.Manager:
-		return s.Stats()
-	default:
-		for _, f := range s.features {
-			if f.Type() == featureType {
-				return f
-			}
+	if s.running {
+		if err := feature.Start(); err != nil {
+			newError("failed to start feature").Base(err).WriteToLog()
 		}
-		return nil
+		return
 	}
-}
 
-// DNSClient returns the dns.Client used by this Instance. The returned dns.Client is always functional.
-func (s *Instance) DNSClient() dns.Client {
-	return &(s.dnsClient)
-}
+	if s.featureResolutions == nil {
+		return
+	}
 
-// PolicyManager returns the policy.Manager used by this Instance. The returned policy.Manager is always functional.
-func (s *Instance) PolicyManager() policy.Manager {
-	return &(s.policyManager)
+	var pendingResolutions []resolution
+	for _, r := range s.featureResolutions {
+		if !r.resolve(s.features) {
+			pendingResolutions = append(pendingResolutions, r)
+		}
+	}
+	if len(pendingResolutions) == 0 {
+		s.featureResolutions = nil
+	} else if len(pendingResolutions) < len(s.featureResolutions) {
+		s.featureResolutions = pendingResolutions
+	}
 }
 
-// Router returns the Router used by this Instance. The returned Router is always functional.
-func (s *Instance) Router() routing.Router {
-	return &(s.router)
+// GetFeature returns a feature of the given type, or nil if such feature is not registered.
+func (s *Instance) GetFeature(featureType interface{}) features.Feature {
+	return getFeature(s.features, featureType)
 }
 
-// Dispatcher returns the Dispatcher used by this Instance. If Dispatcher was not registered before, the returned value doesn't work, although it is not nil.
-func (s *Instance) Dispatcher() routing.Dispatcher {
-	return &(s.dispatcher)
-}
+// Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.
+// A V2Ray instance can be started only once. Upon closing, the instance is not guaranteed to start again.
+func (s *Instance) Start() error {
+	s.access.Lock()
+	defer s.access.Unlock()
 
-// InboundHandlerManager returns the InboundHandlerManager used by this Instance. If InboundHandlerManager was not registered before, the returned value doesn't work.
-func (s *Instance) InboundHandlerManager() inbound.Manager {
-	return &(s.ihm)
-}
+	s.running = true
+	for _, f := range s.features {
+		if err := f.Start(); err != nil {
+			return err
+		}
+	}
 
-// OutboundHandlerManager returns the OutboundHandlerManager used by this Instance. If OutboundHandlerManager was not registered before, the returned value doesn't work.
-func (s *Instance) OutboundHandlerManager() outbound.Manager {
-	return &(s.ohm)
-}
+	newError("V2Ray ", Version(), " started").AtWarning().WriteToLog()
 
-// Stats returns the stats.Manager used by this Instance. If StatManager was not registered before, the returned value doesn't work.
-func (s *Instance) Stats() stats.Manager {
-	return &(s.stats)
+	return nil
 }