simplify dependency resolution

This commit is contained in:
Darien Raymond 2018-10-22 11:26:22 +02:00
parent 9decb3fe36
commit 307aac26b3
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
13 changed files with 135 additions and 56 deletions

View File

@ -1 +1,2 @@
// Package app contains feature implementations of V2Ray. The features may be enabled during runtime.
package app package app

View File

@ -12,7 +12,6 @@ import (
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/signal/done" "v2ray.com/core/common/signal/done"
"v2ray.com/core/features"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
) )
@ -31,9 +30,8 @@ func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
tag: config.Tag, tag: config.Tag,
} }
v := core.MustFromContext(ctx) core.RequireFeatures(ctx, func(om outbound.Manager) {
v.RequireFeatures([]interface{}{outbound.ManagerType()}, func(fs []features.Feature) { c.ohm = om
c.ohm = fs[0].(outbound.Manager)
}) })
for _, rawConfig := range config.Service { for _, rawConfig := range config.Service {

View File

@ -16,7 +16,6 @@ import (
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/session" "v2ray.com/core/common/session"
"v2ray.com/core/common/vio" "v2ray.com/core/common/vio"
"v2ray.com/core/features"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/features/policy" "v2ray.com/core/features/policy"
"v2ray.com/core/features/routing" "v2ray.com/core/features/routing"
@ -94,23 +93,20 @@ type DefaultDispatcher struct {
func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) { func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
d := &DefaultDispatcher{} d := &DefaultDispatcher{}
v := core.MustFromContext(ctx) core.RequireFeatures(ctx, d.Init)
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)
})
return d, nil return d, nil
} }
// Init initializes DefaultDispatcher.
// This method is visible for testing purpose.
func (d *DefaultDispatcher) Init(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) {
d.ohm = om
d.router = router
d.policy = pm
d.stats = sm
}
// Type implements common.HasType. // Type implements common.HasType.
func (*DefaultDispatcher) Type() interface{} { func (*DefaultDispatcher) Type() interface{} {
return routing.DispatcherType() return routing.DispatcherType()

View File

@ -7,14 +7,13 @@ import (
"sync" "sync"
"time" "time"
"v2ray.com/core/features/routing"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/strmatcher" "v2ray.com/core/common/strmatcher"
"v2ray.com/core/features" "v2ray.com/core/features"
"v2ray.com/core/features/dns" "v2ray.com/core/features/dns"
"v2ray.com/core/features/routing"
) )
type Server struct { type Server struct {
@ -43,8 +42,6 @@ func New(ctx context.Context, config *Config) (*Server, error) {
} }
server.hosts = hosts server.hosts = hosts
v := core.MustFromContext(ctx)
addNameServer := func(endpoint *net.Endpoint) int { addNameServer := func(endpoint *net.Endpoint) int {
address := endpoint.Address.AsAddress() address := endpoint.Address.AsAddress()
if address.Family().IsDomain() && address.Domain() == "localhost" { if address.Family().IsDomain() && address.Domain() == "localhost" {
@ -57,9 +54,9 @@ func New(ctx context.Context, config *Config) (*Server, error) {
if dest.Network == net.Network_UDP { if dest.Network == net.Network_UDP {
idx := len(server.servers) idx := len(server.servers)
server.servers = append(server.servers, nil) server.servers = append(server.servers, nil)
v.RequireFeatures([]interface{}{routing.DispatcherType()}, func(fs []features.Feature) {
dispatcher := fs[0].(routing.Dispatcher) core.RequireFeatures(ctx, func(d routing.Dispatcher) {
server.servers[idx] = NewClassicNameServer(dest, dispatcher, server.clientIP) server.servers[idx] = NewClassicNameServer(dest, d, server.clientIP)
}) })
} }
} }
@ -102,6 +99,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
return server, nil return server, nil
} }
// Type implements common.HasType.
func (*Server) Type() interface{} { func (*Server) Type() interface{} {
return dns.ClientType() return dns.ClientType()
} }
@ -123,6 +121,7 @@ func (s *Server) queryIPTimeout(server NameServerInterface, domain string) ([]ne
return ips, err return ips, err
} }
// LookupIP implements dns.Client.
func (s *Server) LookupIP(domain string) ([]net.IP, error) { func (s *Server) LookupIP(domain string) ([]net.IP, error) {
if ip := s.hosts.LookupIP(domain); len(ip) > 0 { if ip := s.hosts.LookupIP(domain); len(ip) > 0 {
return ip, nil return ip, nil

View File

@ -7,7 +7,6 @@ import (
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/features"
"v2ray.com/core/features/inbound" "v2ray.com/core/features/inbound"
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
@ -132,9 +131,9 @@ func (s *service) Register(server *grpc.Server) {
hs := &handlerServer{ hs := &handlerServer{
s: s.v, s: s.v,
} }
s.v.RequireFeatures([]interface{}{inbound.ManagerType(), outbound.ManagerType()}, func(fs []features.Feature) { s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {
hs.ihm = fs[0].(inbound.Manager) hs.ihm = im
hs.ohm = fs[1].(outbound.Manager) hs.ohm = om
}) })
RegisterHandlerServiceServer(server, hs) RegisterHandlerServiceServer(server, hs)
} }

View File

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

View File

@ -9,7 +9,6 @@ import (
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/net" "v2ray.com/core/common/net"
"v2ray.com/core/common/session" "v2ray.com/core/common/session"
"v2ray.com/core/features"
"v2ray.com/core/features/dns" "v2ray.com/core/features/dns"
"v2ray.com/core/features/routing" "v2ray.com/core/features/routing"
"v2ray.com/core/proxy" "v2ray.com/core/proxy"
@ -38,10 +37,10 @@ func NewRouter(ctx context.Context, config *Config) (*Router, error) {
r.rules[idx].Condition = cond r.rules[idx].Condition = cond
} }
v := core.MustFromContext(ctx) core.RequireFeatures(ctx, func(d dns.Client) {
v.RequireFeatures([]interface{}{dns.ClientType()}, func(fs []features.Feature) { r.dns = d
r.dns = fs[0].(dns.Client)
}) })
return r, nil return r, nil
} }

View File

@ -75,17 +75,21 @@ func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest
} }
type service struct { type service struct {
v *core.Instance statsManager feature_stats.Manager
} }
func (s *service) Register(server *grpc.Server) { func (s *service) Register(server *grpc.Server) {
f := s.v.GetFeature(feature_stats.ManagerType()) RegisterStatsServiceServer(server, NewStatsServer(s.statsManager))
RegisterStatsServiceServer(server, NewStatsServer(f.(feature_stats.Manager)))
} }
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
s := core.MustFromContext(ctx) s := new(service)
return &service{v: s}, nil
core.RequireFeatures(ctx, func(sm feature_stats.Manager) {
s.statsManager = sm
})
return s, nil
})) }))
} }

View File

@ -0,0 +1,6 @@
package stats
import "v2ray.com/core/common/errors"
type errPathObjHolder struct {}
func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) }

View File

@ -1,5 +1,7 @@
package stats package stats
//go:generate errorgen
import "v2ray.com/core/features" import "v2ray.com/core/features"
// Counter is the interface for stats counters. // Counter is the interface for stats counters.
@ -36,3 +38,27 @@ func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
func ManagerType() interface{} { func ManagerType() interface{} {
return (*Manager)(nil) return (*Manager)(nil)
} }
// NoopManager is an implementation of Manager, which doesn't has actual functionalities.
type NoopManager struct{}
// Type implements common.HasType.
func (NoopManager) Type() interface{} {
return ManagerType()
}
// RegisterCounter implements Manager.
func (NoopManager) RegisterCounter(string) (Counter, error) {
return nil, newError("not implemented")
}
// GetCounter implements Manager.
func (NoopManager) GetCounter(string) Counter {
return nil
}
// Start implements common.Runnable.
func (NoopManager) Start() error { return nil }
// Close implements common.Closable.
func (NoopManager) Close() error { return nil }

View File

@ -6,8 +6,6 @@ import (
"context" "context"
"time" "time"
"v2ray.com/core/features"
"v2ray.com/core" "v2ray.com/core"
"v2ray.com/core/common" "v2ray.com/core/common"
"v2ray.com/core/common/buf" "v2ray.com/core/common/buf"
@ -37,10 +35,9 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
config: *config, config: *config,
} }
v := core.MustFromContext(ctx) core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) {
v.RequireFeatures([]interface{}{policy.ManagerType(), dns.ClientType()}, func(fs []features.Feature) { f.policyManager = pm
f.policyManager = fs[0].(policy.Manager) f.dns = d
f.dns = fs[1].(dns.Client)
}) })
return f, nil return f, nil

View File

@ -2,6 +2,7 @@ package core
import ( import (
"context" "context"
"reflect"
"sync" "sync"
"v2ray.com/core/common" "v2ray.com/core/common"
@ -12,6 +13,7 @@ import (
"v2ray.com/core/features/outbound" "v2ray.com/core/features/outbound"
"v2ray.com/core/features/policy" "v2ray.com/core/features/policy"
"v2ray.com/core/features/routing" "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. // Server is an instance of V2Ray. At any time, there must be at most one Server instance running.
@ -25,13 +27,13 @@ func ServerType() interface{} {
} }
type resolution struct { type resolution struct {
deps []interface{} deps []reflect.Type
callback func([]features.Feature) callback interface{}
} }
func getFeature(allFeatures []features.Feature, t interface{}) features.Feature { func getFeature(allFeatures []features.Feature, t reflect.Type) features.Feature {
for _, f := range allFeatures { for _, f := range allFeatures {
if f.Type() == t { if reflect.TypeOf(f.Type()) == t {
return f return f
} }
} }
@ -48,7 +50,25 @@ func (r *resolution) resolve(allFeatures []features.Feature) bool {
fs = append(fs, f) fs = append(fs, f)
} }
r.callback(fs) callback := reflect.ValueOf(r.callback)
var input []reflect.Value
callbackType := callback.Type()
for i := 0; i < callbackType.NumIn(); i++ {
pt := callbackType.In(i)
for _, f := range fs {
if reflect.TypeOf(f).AssignableTo(pt) {
input = append(input, reflect.ValueOf(f))
break
}
}
}
if len(input) != callbackType.NumIn() {
panic("Can't get all input parameters")
}
callback.Call(input)
return true return true
} }
@ -112,6 +132,13 @@ func addOutboundHandlers(server *Instance, configs []*OutboundHandlerConfig) err
return nil return nil
} }
// RequireFeatures is a helper function to require features from Instance in context.
// See Instance.RequireFeatures for more information.
func RequireFeatures(ctx context.Context, callback interface{}) {
v := MustFromContext(ctx)
v.RequireFeatures(callback)
}
// New returns a new V2Ray instance based on given configuration. // New returns a new V2Ray instance based on given configuration.
// The instance is not started at this point. // 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. // To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.
@ -151,6 +178,10 @@ func New(config *Config) (*Instance, error) {
server.AddFeature(routing.DefaultRouter{}) server.AddFeature(routing.DefaultRouter{})
} }
if server.GetFeature(stats.ManagerType()) == nil {
server.AddFeature(stats.NoopManager{})
}
// Add an empty instance at the end, for optional feature requirement. // Add an empty instance at the end, for optional feature requirement.
server.AddFeature(&Instance{}) server.AddFeature(&Instance{})
@ -195,7 +226,18 @@ func (s *Instance) Close() error {
} }
// RequireFeatures registers a callback, which will be called when all dependent features are registered. // RequireFeatures registers a callback, which will be called when all dependent features are registered.
func (s *Instance) RequireFeatures(featureTypes []interface{}, callback func([]features.Feature)) { // The callback must be a func(). All its parameters must be features.Feature.
func (s *Instance) RequireFeatures(callback interface{}) {
callbackType := reflect.TypeOf(callback)
if callbackType.Kind() != reflect.Func {
panic("not a function")
}
var featureTypes []reflect.Type
for i := 0; i < callbackType.NumIn(); i++ {
featureTypes = append(featureTypes, reflect.PtrTo(callbackType.In(i)))
}
r := resolution{ r := resolution{
deps: featureTypes, deps: featureTypes,
callback: callback, callback: callback,
@ -236,7 +278,7 @@ func (s *Instance) AddFeature(feature features.Feature) {
// GetFeature returns a feature of the given type, or nil if such feature is not registered. // GetFeature returns a feature of the given type, or nil if such feature is not registered.
func (s *Instance) GetFeature(featureType interface{}) features.Feature { func (s *Instance) GetFeature(featureType interface{}) features.Feature {
return getFeature(s.features, featureType) return getFeature(s.features, reflect.TypeOf(featureType))
} }
// Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown. // Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.

View File

@ -13,12 +13,27 @@ import (
"v2ray.com/core/common/protocol" "v2ray.com/core/common/protocol"
"v2ray.com/core/common/serial" "v2ray.com/core/common/serial"
"v2ray.com/core/common/uuid" "v2ray.com/core/common/uuid"
"v2ray.com/core/features/dns"
_ "v2ray.com/core/main/distro/all" _ "v2ray.com/core/main/distro/all"
"v2ray.com/core/proxy/dokodemo" "v2ray.com/core/proxy/dokodemo"
"v2ray.com/core/proxy/vmess" "v2ray.com/core/proxy/vmess"
"v2ray.com/core/proxy/vmess/outbound" "v2ray.com/core/proxy/vmess/outbound"
) )
func TestV2RayDependency(t *testing.T) {
instance := new(Instance)
wait := make(chan bool, 1)
instance.RequireFeatures(func(d dns.Client) {
if d == nil {
t.Error("expected dns client fulfilled, but actually nil")
}
wait <- true
})
instance.AddFeature(dns.LocalClient{})
<-wait
}
func TestV2RayClose(t *testing.T) { func TestV2RayClose(t *testing.T) {
port := net.Port(dice.RollUint16()) port := net.Port(dice.RollUint16())
userId := uuid.New() userId := uuid.New()